Skip to content

Conversation

@rstam
Copy link
Contributor

@rstam rstam commented May 27, 2025

No description provided.

@rstam rstam requested review from adelinowona and sanych-sun May 27, 2025 18:33
@rstam rstam added bug Fixes issues or unintended behavior. improvement Optimizations or refactoring (no new features or fixes). labels May 27, 2025
@rstam rstam changed the title CSHARP-5572: Implement new KnownSerializerFinder. CSHARP-5572: Implement new KnownSerializerFinder May 27, 2025
@Szmiglo
Copy link

Szmiglo commented Jul 23, 2025

hi @rstam, what's the status of this effort?

@rstam
Copy link
Contributor Author

rstam commented Jul 23, 2025

hi @rstam, what's the status of this effort?

It is progressing well, but it is a large effort. I work on this whenever I'm not working on some smaller higher priority task.

@rstam rstam requested review from a team and removed request for a team August 21, 2025 15:25
@rstam rstam force-pushed the csharp5572 branch 2 times, most recently from 86fbb24 to 56a464c Compare October 13, 2025 15:15
}
}

if (_map.TryGetValue(node, out var existingSerializer))
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should move the duplicate check to the beginning of the AddSerializer method to implement fail-fast behavior. This would avoid unnecessary serializer transformation work when a serializer already exists for the node.

However, I notice the current implementation places this check at the end, likely to preserve information about the conflicting serializer for the exception. While this debugging information could be helpful, I question just how useful it is. Knowing where a duplicate attempt happened seems more useful knowing what the conflicting serializer would have been.

I lean toward the fail-fast approach since the conflicting serializer details seem like a "nice-to-have" rather than essential debugging information.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we move the error check further up then we have to remove information about the redundant serializer from the error message.

Also, I don't think we need to worry about failing fast because we actually never expect to hit this error. This check is here to help us find bugs. In the absence of bugs this exception will never be thrown.

private bool _isMakingProgress = true;
private readonly KnownSerializerMap _knownSerializers;
private int _oldKnownSerializersCount = 0;
private int _pass = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like this is never used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed.

var newKnownSerializersCount = _knownSerializers.Count;
if (newKnownSerializersCount == _oldKnownSerializersCount)
{
if (_useDefaultSerializerForConstants)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we maybe make use of a state enum as such:

private enum DiscoveryPhase
{
    InferringFromContext,      // Not using defaults
    UsingDefaultSerializers,   // Last attempt with defaults
    Complete                    // No more progress possible
}

to replace the use of all these booleans used in keeping track of the state of the visitor?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's somewhat reasonable, but I think the booleans work better here.

There are only 2 booleans.

One issue is that if we ever had more states the order of the enum values could get very confusing and maybe even no order is possible.

@sanych-sun sanych-sun marked this pull request as ready for review November 25, 2025 22:22
@sanych-sun sanych-sun requested a review from a team as a code owner November 25, 2025 22:22
Copilot AI review requested due to automatic review settings November 25, 2025 22:22
Copy link
Member

@sanych-sun sanych-sun left a comment

Choose a reason for hiding this comment

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

Partial review.


namespace MongoDB.Driver.Linq.Linq3Implementation.KnownSerializerFinders;

internal static class KnownSerializerFinder
Copy link
Member

Choose a reason for hiding this comment

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

Term Known in the class name suggests there might be unknown serializers. But as far as I remember from our meetings currently we make sure each expression MUST have serializer assigned. May be we should rename it to SerializerFinder?


public bool IsKnown(Expression node)
{
return node != null && _map.ContainsKey(node);
Copy link
Member

Choose a reason for hiding this comment

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

Is there any real use cases when we pass node=null? Sounds like we should throw instead.

DeduceUnknownMethodSerializer();
}

bool IsCollectionContainsMethod(out Expression collectionExpression, out Expression itemExpression)
Copy link
Member

Choose a reason for hiding this comment

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

We already have similar detection in at least 2 places:
ContainsMethodToAggregationExpressionTranslator,
ContainsMethodToFilterTranslator

Now it's going to be 3rd. Should we consolidate this into some helper method?

DeduceUnknownMethodSerializer();
}

bool IsContainsValueInstanceMethod(out Expression collectionExpression, out Expression valueExpression)
Copy link
Member

Choose a reason for hiding this comment

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

We have very similar logic in:
ContainsValueMethodToFilterTranslator,
ContainsValueMethodToAggregationExpressionTranslator

Now it's going to be 3rd. Should we consolidate this into some helper method?

DeduceUnknownMethodSerializer();
}

static bool IsSetEqualsMethod(MethodInfo method)
Copy link
Member

Choose a reason for hiding this comment

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

Similar checks are done in:
SetEqualsMethodToAggregationExpressionTranslator,

Should we consolidate into some helper method?

_ when collectionType.IsArray => ArraySerializer.Create(itemSerializer),
_ when collectionType.IsConstructedGenericType && collectionType.GetGenericTypeDefinition() == typeof(IEnumerable<>) => IEnumerableSerializer.Create(itemSerializer),
_ when collectionType.IsConstructedGenericType && collectionType.GetGenericTypeDefinition() == typeof(IOrderedEnumerable<>) => IOrderedEnumerableSerializer.Create(itemSerializer),
_ when collectionType.IsConstructedGenericType && collectionType.GetGenericTypeDefinition() == typeof(IQueryable<>) => IQueryableSerializer.Create(itemSerializer),
Copy link
Member

Choose a reason for hiding this comment

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

Same for IQueryable: it implements IEnumerable


return node;

void DeduceConditionaSerializers()
Copy link
Member

Choose a reason for hiding this comment

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

Typo: "ConditionaL"

bool IsDictionaryIndexer()
{
return
indexer.DeclaringType.Name.Contains("Dictionary") &&
Copy link
Member

Choose a reason for hiding this comment

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

Are we really need to validate the class name? Is there any better way to do that? For example Hashtable or SortedList implements IDictionary, but it has no Dictionary in the name.


bool IsTupleOrValueTuple(Type type)
{
return
Copy link
Member

Choose a reason for hiding this comment

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

For net6 and higher there is a better way to check: both Tuple and ValueTuple implements ITuple interface. I suppose we can have #if def here and use interface approach for modern targets, when have something like the proposed code for net472.

throw new ExpressionNotSupportedException(expression, because: "target type is not an enum");
}

// if (sourceSerializer is IHasRepresentationSerializer sourceHasRepresentationSerializer &&
Copy link
Member

Choose a reason for hiding this comment

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

Do we need this commented code?

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 implements a new KnownSerializerFinder system to replace the previous approach of dynamically determining serializers during expression translation. The new system pre-analyzes expressions to build a map of known serializers for each expression node, enabling more reliable and consistent serializer resolution throughout the translation process.

Key Changes:

  • Introduced a comprehensive KnownSerializerFinder infrastructure that pre-computes serializers for expression nodes
  • Updated TranslationContext to accept and use the pre-computed serializer map
  • Refactored multiple translators to leverage the new known serializer system
  • Added several new serializer types to support the enhanced type inference

Reviewed changes

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

Show a summary per file
File Description
KnownSerializerFinder*.cs New visitor-based system for discovering and mapping serializers to expression nodes
TranslationContext.cs Extended to store and provide access to known serializers map
LinqProviderAdapter.cs Updated all translation entry points to use the new KnownSerializerFinder
*Serializer.cs Added new serializer types (Polymorphic, Upcasting, Unknowable, etc.) to support enhanced type inference
Test files Updated test contexts to use new TranslationContext.Create signatures

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

Comment on lines +27 to +33
DeduceSerialiers();
base.VisitListInit(node);
DeduceSerialiers();

return node;

void DeduceSerialiers()
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

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

Corrected spelling of 'DeduceSerialiers' to 'DeduceSerializers'.

Suggested change
DeduceSerialiers();
base.VisitListInit(node);
DeduceSerialiers();
return node;
void DeduceSerialiers()
DeduceSerializers();
base.VisitListInit(node);
DeduceSerializers();
return node;
void DeduceSerializers()

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +33
DeduceConditionaSerializers();
base.VisitConditional(node);
DeduceConditionaSerializers();

return node;

void DeduceConditionaSerializers()
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

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

Corrected spelling of 'DeduceConditionaSerializers' to 'DeduceConditionalSerializers'.

Suggested change
DeduceConditionaSerializers();
base.VisitConditional(node);
DeduceConditionaSerializers();
return node;
void DeduceConditionaSerializers()
DeduceConditionalSerializers();
base.VisitConditional(node);
DeduceConditionalSerializers();
return node;
void DeduceConditionalSerializers()

Copilot uses AI. Check for mistakes.
return sourceSerializer;
}

// handle conversionsn to BsonValue before any others
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

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

Corrected spelling of 'conversionsn' to 'conversions'.

Suggested change
// handle conversionsn to BsonValue before any others
// handle conversion to BsonValue before any others

Copilot uses AI. Check for mistakes.
_knownSerializers = knownSerializers;
}

public Expression ExpressionWithUnknownSerialier => _expressionWithUnknownSerializer;
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

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

Corrected spelling of 'ExpressionWithUnknownSerialier' to 'ExpressionWithUnknownSerializer'.

Suggested change
public Expression ExpressionWithUnknownSerialier => _expressionWithUnknownSerializer;
public Expression ExpressionWithUnknownSerializer => _expressionWithUnknownSerializer;

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. improvement Optimizations or refactoring (no new features or fixes).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants