From 14f78677bce6e8942c108328721b3d37b556acf6 Mon Sep 17 00:00:00 2001 From: Daniel Svensson Date: Thu, 25 Jun 2026 14:03:05 +0200 Subject: [PATCH 1/4] Fix: System.InvalidOperationException : The declared derived type 'a' is not a valid subtype of 'b'. For inheritance scenarios --- .../ReflectionTypeShapeProvider.cs | 11 +++++---- .../PolyType.Tests/DataContractShapeTests.cs | 24 +++++++++++++++++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs b/src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs index 64a0febb..e071bc17 100644 --- a/src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs +++ b/src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs @@ -294,13 +294,13 @@ private IUnionTypeShape CreateUnionTypeShape(Type unionType, FSharpUnionInfo? fS return (IUnionTypeShape)Activator.CreateInstance(fsharpUnionTypeTy, fSharpUnionInfo, this, options)!; } - List derivedTypeAttributes = unionType.GetCustomAttributes().ToList(); + List derivedTypeAttributes = unionType.GetCustomAttributes(inherit: false).ToList(); // Honor KnownTypeAttribute annotations only when no DerivedTypeShapeAttribute is present, // which takes precedence over KnownTypeAttribute. if (derivedTypeAttributes.Count == 0) { - var mappedKnownTypeAttributes = unionType.GetCustomAttributes() + var mappedKnownTypeAttributes = unionType.GetCustomAttributes(inherit: false) .Select(attr => { if (attr.Type is null) @@ -308,8 +308,11 @@ private IUnionTypeShape CreateUnionTypeShape(Type unionType, FSharpUnionInfo? fS throw new NotSupportedException("KnownTypeAttribute annotations using methods are not supported."); } - return new DerivedTypeShapeAttribute(attr.Type); - }); + return attr.Type; + }) + // Only consider derived types, KnownTypeAttribute can be used for AssociatedTypes purposes + .Where(type => unionType.IsAssignableFrom(type) && type != unionType) + .Select(t => new DerivedTypeShapeAttribute(t)); derivedTypeAttributes.AddRange(mappedKnownTypeAttributes); } diff --git a/tests/PolyType.Tests/DataContractShapeTests.cs b/tests/PolyType.Tests/DataContractShapeTests.cs index 79929bed..4ff8d850 100644 --- a/tests/PolyType.Tests/DataContractShapeTests.cs +++ b/tests/PolyType.Tests/DataContractShapeTests.cs @@ -89,9 +89,19 @@ public void KnownTypeAttribute_ReportsUnionShape() { var shape = Assert.IsType(providerUnderTest.Provider.GetTypeShape(typeof(Animal)), exactMatch: false); - Assert.Equal(2, shape.UnionCases.Count); + Assert.Equal(3, shape.UnionCases.Count); Assert.Contains(shape.UnionCases, c => c.UnionCaseType.Type == typeof(Dog)); Assert.Contains(shape.UnionCases, c => c.UnionCaseType.Type == typeof(Cat)); + Assert.Contains(shape.UnionCases, c => c.UnionCaseType.Type == typeof(PersianCat)); + } + + [Fact] + public void KnownTypeAttribute_DerivedTypesReportsUnionShape() + { + var shape = Assert.IsType(providerUnderTest.Provider.GetTypeShape(typeof(Cat)), exactMatch: false); + + Assert.Single(shape.UnionCases); + Assert.Contains(shape.UnionCases, c => c.UnionCaseType.Type == typeof(PersianCat)); } [Fact] @@ -193,6 +203,7 @@ public enum ContractEnum [DataContract] [KnownType(typeof(Dog))] [KnownType(typeof(Cat))] + [KnownType(typeof(PersianCat))] public partial class Animal { [DataMember(Order = 0)] public string? Name { get; set; } @@ -204,12 +215,21 @@ public class Dog : Animal [DataMember(Order = 1)] public bool Barks { get; set; } } + [GenerateShape] [DataContract] - public class Cat : Animal + [KnownType(typeof(PersianCat))] + public partial class Cat : Animal { [DataMember(Order = 1)] public int Lives { get; set; } } + [DataContract] + public class PersianCat : Cat + { + [DataMember(Order = 2)] public string? FurColor { get; set; } + [DataMember(Order = 3)] public bool RequiresGrooming { get; set; } + } + [GenerateShape] [KnownType(typeof(DogNoDataContract))] public partial class AnimalNoDataContract From e1edf449f50f0c22048019b0dea4ad0ffc27cad2 Mon Sep 17 00:00:00 2001 From: Daniel Svensson Date: Thu, 2 Jul 2026 12:09:11 +0200 Subject: [PATCH 2/4] Simplify where --- src/PolyType.SourceGenerator/Parser/Parser.cs | 6 ++++++ .../ReflectionProvider/ReflectionTypeShapeProvider.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/PolyType.SourceGenerator/Parser/Parser.cs b/src/PolyType.SourceGenerator/Parser/Parser.cs index 4e9dee9f..c62de97f 100644 --- a/src/PolyType.SourceGenerator/Parser/Parser.cs +++ b/src/PolyType.SourceGenerator/Parser/Parser.cs @@ -615,6 +615,12 @@ protected override IEnumerable ResolveDerivedTypes(ITypeSymbol } derivedType = dt; + + // Only consider derived types, KnownTypeAttribute can be used for AssociatedTypes purposes + if (!type.IsAssignableFrom(derivedType)) + { + continue; + } } else { diff --git a/src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs b/src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs index e071bc17..bd1477c7 100644 --- a/src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs +++ b/src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs @@ -311,7 +311,7 @@ private IUnionTypeShape CreateUnionTypeShape(Type unionType, FSharpUnionInfo? fS return attr.Type; }) // Only consider derived types, KnownTypeAttribute can be used for AssociatedTypes purposes - .Where(type => unionType.IsAssignableFrom(type) && type != unionType) + .Where(type => unionType.IsAssignableFrom(type)) .Select(t => new DerivedTypeShapeAttribute(t)); derivedTypeAttributes.AddRange(mappedKnownTypeAttributes); From 53b7c7ed2dbd28ff92f4d5378e9c02a70edb1d35 Mon Sep 17 00:00:00 2001 From: Daniel Svensson Date: Thu, 2 Jul 2026 16:42:27 +0200 Subject: [PATCH 3/4] Apply suggestion from @Daniel-Svensson --- src/PolyType.SourceGenerator/Parser/Parser.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/PolyType.SourceGenerator/Parser/Parser.cs b/src/PolyType.SourceGenerator/Parser/Parser.cs index c62de97f..4e9dee9f 100644 --- a/src/PolyType.SourceGenerator/Parser/Parser.cs +++ b/src/PolyType.SourceGenerator/Parser/Parser.cs @@ -615,12 +615,6 @@ protected override IEnumerable ResolveDerivedTypes(ITypeSymbol } derivedType = dt; - - // Only consider derived types, KnownTypeAttribute can be used for AssociatedTypes purposes - if (!type.IsAssignableFrom(derivedType)) - { - continue; - } } else { From 3fe9bcc953f68fe38846af1949a0562c0dcfe7ce Mon Sep 17 00:00:00 2001 From: Daniel Svensson Date: Thu, 9 Jul 2026 13:53:01 +0200 Subject: [PATCH 4/4] Update src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs --- src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs b/src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs index bd1477c7..2c10cfc7 100644 --- a/src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs +++ b/src/PolyType/ReflectionProvider/ReflectionTypeShapeProvider.cs @@ -310,8 +310,6 @@ private IUnionTypeShape CreateUnionTypeShape(Type unionType, FSharpUnionInfo? fS return attr.Type; }) - // Only consider derived types, KnownTypeAttribute can be used for AssociatedTypes purposes - .Where(type => unionType.IsAssignableFrom(type)) .Select(t => new DerivedTypeShapeAttribute(t)); derivedTypeAttributes.AddRange(mappedKnownTypeAttributes);