Skip to content

Commit 46162a4

Browse files
committed
Suppress SHARPYAML002 when a converter handles the type
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.
1 parent bc166ce commit 46162a4

2 files changed

Lines changed: 486 additions & 2 deletions

File tree

src/SharpYaml.SourceGenerator/YamlSerializerContextGenerator.cs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,10 +407,23 @@ private static void EmitContext(SourceProductionContext context, Compilation com
407407
continue;
408408
}
409409

410+
// Skip if the member itself has [YamlConverter(typeof(...))] — the converter handles serialization.
411+
if (GetYamlConverterAttributeTypeName(member) is not null)
412+
{
413+
continue;
414+
}
415+
416+
// Skip if the member type is handled by a converter (type-level attribute or context-level converter).
417+
if (IsTypeHandledByConverter(memberType, model.SourceGenerationOptions.ConverterTypes, compilation))
418+
{
419+
continue;
420+
}
421+
410422
if (TryGetArrayElementType(memberType, out var arrayElementType) ||
411423
TryGetSequenceElementType(memberType, out arrayElementType, out _))
412424
{
413-
if (IsKnownScalar(arrayElementType) || indexByType.ContainsKey(arrayElementType))
425+
if (IsKnownScalar(arrayElementType) || indexByType.ContainsKey(arrayElementType) ||
426+
IsTypeHandledByConverter(arrayElementType, model.SourceGenerationOptions.ConverterTypes, compilation))
414427
{
415428
continue;
416429
}
@@ -437,7 +450,8 @@ private static void EmitContext(SourceProductionContext context, Compilation com
437450
continue;
438451
}
439452

440-
if (IsKnownScalar(dictionaryValueType) || indexByType.ContainsKey(dictionaryValueType))
453+
if (IsKnownScalar(dictionaryValueType) || indexByType.ContainsKey(dictionaryValueType) ||
454+
IsTypeHandledByConverter(dictionaryValueType, model.SourceGenerationOptions.ConverterTypes, compilation))
441455
{
442456
continue;
443457
}
@@ -5879,6 +5893,55 @@ private static string GetIgnoreConditionExpression(ISymbol member)
58795893
return null;
58805894
}
58815895

5896+
/// <summary>
5897+
/// Checks whether the given type is handled by a converter — either via a [YamlConverter] attribute
5898+
/// on the type itself, or via a context-level YamlConverter&lt;T&gt; registration.
5899+
/// Nullable&lt;T&gt; value types are unwrapped before checking.
5900+
/// </summary>
5901+
private static bool IsTypeHandledByConverter(
5902+
ITypeSymbol typeToCheck,
5903+
ImmutableArray<ITypeSymbol> converterTypes,
5904+
Compilation compilation)
5905+
{
5906+
// Unwrap Nullable<T> for value types.
5907+
var unwrappedType = typeToCheck;
5908+
if (typeToCheck is INamedTypeSymbol nullable && nullable.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
5909+
{
5910+
unwrappedType = nullable.TypeArguments[0];
5911+
}
5912+
5913+
// Check if the type itself has [YamlConverter(typeof(...))].
5914+
if (GetYamlConverterAttributeTypeName(unwrappedType) is not null)
5915+
{
5916+
return true;
5917+
}
5918+
5919+
// Check if a context-level converter handles this type.
5920+
if (!converterTypes.IsDefaultOrEmpty)
5921+
{
5922+
var yamlConverterOfT = compilation.GetTypeByMetadataName("SharpYaml.Serialization.YamlConverter`1");
5923+
if (yamlConverterOfT is not null)
5924+
{
5925+
foreach (var converterType in converterTypes)
5926+
{
5927+
// Walk up the base type chain looking for YamlConverter<T>.
5928+
for (var current = converterType as INamedTypeSymbol; current is not null; current = current.BaseType)
5929+
{
5930+
if (current.IsGenericType &&
5931+
SymbolEqualityComparer.Default.Equals(current.OriginalDefinition, yamlConverterOfT) &&
5932+
current.TypeArguments.Length == 1 &&
5933+
SymbolEqualityComparer.Default.Equals(current.TypeArguments[0], unwrappedType))
5934+
{
5935+
return true;
5936+
}
5937+
}
5938+
}
5939+
}
5940+
}
5941+
5942+
return false;
5943+
}
5944+
58825945
private static ImmutableArray<ISymbol> GetSerializableMembers(INamedTypeSymbol type)
58835946
{
58845947
// Arrays/collections/dictionaries are handled by dedicated generated code paths, not as object graphs.

0 commit comments

Comments
 (0)