-
Notifications
You must be signed in to change notification settings - Fork 1.3k
CSHARP-5572: Implement new KnownSerializerFinder #1700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
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. |
86fbb24 to
56a464c
Compare
| } | ||
| } | ||
|
|
||
| if (_map.TryGetValue(node, out var existingSerializer)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this 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 |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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") && |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 && |
There was a problem hiding this comment.
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?
There was a problem hiding this 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
KnownSerializerFinderinfrastructure that pre-computes serializers for expression nodes - Updated
TranslationContextto 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.
| DeduceSerialiers(); | ||
| base.VisitListInit(node); | ||
| DeduceSerialiers(); | ||
|
|
||
| return node; | ||
|
|
||
| void DeduceSerialiers() |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
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'.
| DeduceSerialiers(); | |
| base.VisitListInit(node); | |
| DeduceSerialiers(); | |
| return node; | |
| void DeduceSerialiers() | |
| DeduceSerializers(); | |
| base.VisitListInit(node); | |
| DeduceSerializers(); | |
| return node; | |
| void DeduceSerializers() |
| DeduceConditionaSerializers(); | ||
| base.VisitConditional(node); | ||
| DeduceConditionaSerializers(); | ||
|
|
||
| return node; | ||
|
|
||
| void DeduceConditionaSerializers() |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
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'.
| DeduceConditionaSerializers(); | |
| base.VisitConditional(node); | |
| DeduceConditionaSerializers(); | |
| return node; | |
| void DeduceConditionaSerializers() | |
| DeduceConditionalSerializers(); | |
| base.VisitConditional(node); | |
| DeduceConditionalSerializers(); | |
| return node; | |
| void DeduceConditionalSerializers() |
| return sourceSerializer; | ||
| } | ||
|
|
||
| // handle conversionsn to BsonValue before any others |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
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'.
| // handle conversionsn to BsonValue before any others | |
| // handle conversion to BsonValue before any others |
| _knownSerializers = knownSerializers; | ||
| } | ||
|
|
||
| public Expression ExpressionWithUnknownSerialier => _expressionWithUnknownSerializer; |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
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'.
| public Expression ExpressionWithUnknownSerialier => _expressionWithUnknownSerializer; | |
| public Expression ExpressionWithUnknownSerializer => _expressionWithUnknownSerializer; |
No description provided.