Skip to content

CSHARP-5920: SerializerFinder wrapping serializer into Upcast/Downcast serializer breaks some expressions translation#1909

Draft
sanych-sun wants to merge 1 commit intomongodb:mainfrom
sanych-sun:csharp5920
Draft

CSHARP-5920: SerializerFinder wrapping serializer into Upcast/Downcast serializer breaks some expressions translation#1909
sanych-sun wants to merge 1 commit intomongodb:mainfrom
sanych-sun:csharp5920

Conversation

@sanych-sun
Copy link
Member

No description provided.

…t serializer breaks some expressions translation
@sanych-sun sanych-sun requested a review from a team as a code owner March 11, 2026 02:42
@sanych-sun sanych-sun requested review from Copilot and papafe March 11, 2026 02:42
@sanych-sun sanych-sun marked this pull request as draft March 11, 2026 02:42
@sanych-sun sanych-sun requested review from adelinowona and damieng and removed request for papafe March 11, 2026 02:42
@sanych-sun sanych-sun added the bug Fixes issues or unintended behavior. label Mar 11, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses LINQ3 expression translation issues caused by automatically wrapping serializers with upcast/downcast wrappers in SerializerFinder (CSHARP-5920), moving that wrapping logic to explicit call sites instead of applying it globally in the serializer map.

Changes:

  • Removed automatic upcast/downcast wrapping from SerializerMap.AddSerializer and added a DEBUG-only invariant check.
  • Introduced UpcastOrDowncastSerializer helper and applied it to selected serializer deduction paths (collections, HasFlag, and new-array init serializer assignment).
  • Adjusted serializer deduction to better align serializer ValueType with the expression/node type in the affected scenarios.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/MongoDB.Driver/Linq/Linq3Implementation/SerializerFinders/SerializerMap.cs Stops implicitly wrapping mismatched serializers; adds DEBUG-time validation.
src/MongoDB.Driver/Linq/Linq3Implementation/SerializerFinders/SerializerFinderVisitNewArray.cs Ensures the array serializer is explicitly upcast/downcasted to the node type before storing.
src/MongoDB.Driver/Linq/Linq3Implementation/SerializerFinders/SerializerFinderVisitMethodCall.cs Applies explicit upcast/downcast for Enum.HasFlag argument serializer deduction.
src/MongoDB.Driver/Linq/Linq3Implementation/SerializerFinders/SerializerFinderHelperMethods.cs Adds UpcastOrDowncastSerializer helper and uses it when building/deducing collection serializers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +63 to +70
#if DEBUG

if (serializer.ValueType != node.Type)
{
if (node.Type.IsAssignableFrom(serializer.ValueType))
{
serializer = DowncastingSerializer.Create(baseType: node.Type, derivedType: serializer.ValueType, derivedTypeSerializer: serializer);
}
else if (serializer.ValueType.IsAssignableFrom(node.Type))
{
serializer = UpcastingSerializer.Create(baseType: serializer.ValueType, derivedType: node.Type, baseTypeSerializer: serializer);
}
else
{
throw new ArgumentException($"Serializer value type {serializer.ValueType} does not match expression value type {node.Type}", nameof(serializer));
}
throw new InvalidOperationException($"Serializer type {serializer.GetType()} does not match node type {node.Type}.");
}

#endif
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddSerializer no longer normalizes serializer/value type mismatches in release builds (the check is inside #if DEBUG). That means in non-DEBUG builds a serializer whose ValueType differs from node.Type will be stored silently, which can lead to incorrect translation/serialization later. Consider enforcing the invariant in all builds (throwing an ArgumentException is likely more appropriate here), and rely on explicit UpcastOrDowncastSerializer at call sites when a conversion is actually intended.

Copilot uses AI. Check for mistakes.
Comment on lines 113 to 118
var itemType = node.Type.GetElementType();
arraySerializer = PolymorphicArraySerializer.Create(itemType, itemSerializers);
}

arraySerializer = UpcastOrDowncastSerializer(node.Type, arraySerializer);
AddNodeSerializer(node, arraySerializer);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the array serializer is being upcast/downcasted here. In this visitor, item serializers are added earlier via AddNodeSerializer(itemExpression, itemSerializer) (including for IPolymorphicArraySerializer), and PolymorphicArraySerializer explicitly allows derived itemSerializer.ValueType values. Now that SerializerMap.AddSerializer no longer auto-wraps mismatches, those earlier item mappings can end up with serializer.ValueType != itemExpression.Type (and will throw in DEBUG). Consider applying UpcastOrDowncastSerializer(itemExpression.Type, itemSerializer) when adding item serializers as well.

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +100
_ when collectionType.IsConstructedGenericType && collectionType.GetGenericTypeDefinition() == typeof(IOrderedEnumerable<>) => IOrderedEnumerableSerializer.Create(UpcastOrDowncastSerializer(collectionType.GetGenericArguments()[0],itemSerializer)),
_ when collectionType.IsConstructedGenericType && collectionType.GetGenericTypeDefinition() == typeof(IQueryable<>) => IQueryableSerializer.Create(UpcastOrDowncastSerializer(collectionType.GetGenericArguments()[0],itemSerializer)),
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing whitespace after the comma in the UpcastOrDowncastSerializer calls (e.g. ...,itemSerializer)). This breaks the surrounding formatting style (other argument lists include a space after commas) and makes the line harder to read.

Suggested change
_ when collectionType.IsConstructedGenericType && collectionType.GetGenericTypeDefinition() == typeof(IOrderedEnumerable<>) => IOrderedEnumerableSerializer.Create(UpcastOrDowncastSerializer(collectionType.GetGenericArguments()[0],itemSerializer)),
_ when collectionType.IsConstructedGenericType && collectionType.GetGenericTypeDefinition() == typeof(IQueryable<>) => IQueryableSerializer.Create(UpcastOrDowncastSerializer(collectionType.GetGenericArguments()[0],itemSerializer)),
_ when collectionType.IsConstructedGenericType && collectionType.GetGenericTypeDefinition() == typeof(IOrderedEnumerable<>) => IOrderedEnumerableSerializer.Create(UpcastOrDowncastSerializer(collectionType.GetGenericArguments()[0], itemSerializer)),
_ when collectionType.IsConstructedGenericType && collectionType.GetGenericTypeDefinition() == typeof(IQueryable<>) => IQueryableSerializer.Create(UpcastOrDowncastSerializer(collectionType.GetGenericArguments()[0], itemSerializer)),

Copilot uses AI. Check for mistakes.
Comment on lines +255 to +273
private static IBsonSerializer UpcastOrDowncastSerializer(Type type, IBsonSerializer serializer)
{
if (serializer.ValueType == type)
{
return serializer;
}

if (type.IsAssignableFrom(serializer.ValueType))
{
return DowncastingSerializer.Create(baseType: type, derivedType: serializer.ValueType, derivedTypeSerializer: serializer);
}

if (serializer.ValueType.IsAssignableFrom(type))
{
return UpcastingSerializer.Create(baseType: serializer.ValueType, derivedType: type, baseTypeSerializer: serializer);
}

throw new ArgumentException($"Serializer value type {serializer.ValueType} does not match expression value type {type}", nameof(serializer));
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UpcastOrDowncastSerializer introduces new, central type-mapping behavior that can affect LINQ translation outcomes. There are extensive Linq3 translation and serializer unit tests in the repo; adding a focused regression test for the CSHARP-5920 scenario (and for upcast/downcast cases, including polymorphic array items) would help prevent future regressions in this area.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Fixes issues or unintended behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants