Repository: dotnet/efcore
File: src/EFCore.Relational/Query/RelationalTypeMappingPostprocessor.cs (C#)

Code before:
            {
                RelationalTypeMapping? collectionParameterTypeMapping;

                if (inferredTypeMappings[1] is { } elementTypeMapping)
                {
                    // In the usual case, some operation performed against the elements of the collection (e.g. comparison to a column) provides us with
                    // an element type mapping; infer the collection's type mapping from that.
                    collectionParameterTypeMapping = RelationalDependencies.TypeMappingSource.FindMapping(
                            valuesParameter.Type, QueryCompilationContext.Model, elementTypeMapping);

                    if (collectionParameterTypeMapping is not { ElementTypeMapping: not null })
                    {
                        throw new UnreachableException("A RelationalTypeMapping collection type mapping could not be found");
                    }
                }
                else
                {
                    // We have no inferred type mapping for the element type. This means that there's was nothing in the query done
                    // against the elements of the collection (e.g. comparison to a column), which tells us what type mapping it is.
                    // In normal circumstances, such an expression would get client-evaluated in the funceltizer (no reference to a
                    // column/database-side object), but with compiled queries the collection parameter gets preserved as-is.
                    // The only thing we can do is apply the default type mapping.
                    collectionParameterTypeMapping = RelationalDependencies.TypeMappingSource.FindMapping(valuesParameter.Type, QueryCompilationContext.Model);
                }

                return valuesExpression.Update(valuesParameter.ApplyTypeMapping(collectionParameterTypeMapping));
            }

            default:
                throw new UnreachableException();
        }
    }

    /// <summary>
    ///     A visitor which scans an expression tree and attempts to find columns for which we were missing type mappings (projected out
    ///     of queryable constant/parameter), and those type mappings have been inferred.
    /// </summary>
    /// <remarks>
    ///     <para>
    ///         This handles two cases: (1) an untyped column which type-inferred in the regular way, e.g. through comparison to a typed
    ///         column, and (2) set operations where on side is typed and the other is untyped.
    ///     </para>
    ///     <para>
    ///         Note that this visitor follows type columns across subquery projections. That is, if a root constant/parameter is buried
    ///         within subqueries, and somewhere above the column projected out of a subquery is inferred, this is picked up and propagated
    ///         all the way down.
    ///     </para>
    ///     <para>
    ///         The visitor does not change the query tree in any way - it only populates the inferred type mappings it identified in
    ///         the given dictionary; actual application of the inferred type mappings happens later in
    ///         <see cref="RelationalTypeMappingPostprocessor" />. We can't do this in a single pass since untyped roots

Review comment (suggestion):
The code path at line 201 doesn't validate that the returned collectionParameterTypeMapping has a non-null ElementTypeMapping before using it at line 204. In the else branch starting at line 194, when calling FindMapping without an element type mapping, there should be a null check similar to the one at lines 189-192 to ensure the returned type mapping has a valid ElementTypeMapping before calling Update.

Code after:
            {
                RelationalTypeMapping? collectionParameterTypeMapping;

                if (inferredTypeMappings[1] is { } elementTypeMapping)
                {
                    // In the usual case, some operation performed against the elements of the collection (e.g. comparison to a column) provides us with
                    // an element type mapping; infer the collection's type mapping from that.
                    collectionParameterTypeMapping = RelationalDependencies.TypeMappingSource.FindMapping(
                            valuesParameter.Type, QueryCompilationContext.Model, elementTypeMapping);
                }
                else
                {
                    // We have no inferred type mapping for the element type. This means that there was nothing in the query done
                    // against the elements of the collection (e.g. comparison to a column), which tells us what type mapping it is.
                    // In normal circumstances, such an expression would get client-evaluated in the funceltizer (no reference to a
                    // column/database-side object), but with compiled queries the collection parameter gets preserved as-is.
                    // The only thing we can do is apply the default type mapping.
                    collectionParameterTypeMapping = RelationalDependencies.TypeMappingSource.FindMapping(valuesParameter.Type, QueryCompilationContext.Model);
                }

                if (collectionParameterTypeMapping is not { ElementTypeMapping: not null })
                {
                    throw new UnreachableException("A RelationalTypeMapping collection type mapping could not be found");
                }

                return valuesExpression.Update(valuesParameter.ApplyTypeMapping(collectionParameterTypeMapping));
            }

            default:
                throw new UnreachableException();
        }
    }

    /// <summary>
    ///     A visitor which scans an expression tree and attempts to find columns for which we were missing type mappings (projected out
    ///     of queryable constant/parameter), and those type mappings have been inferred.
    /// </summary>
    /// <remarks>
    ///     <para>
    ///         This handles two cases: (1) an untyped column which type-inferred in the regular way, e.g. through comparison to a typed
    ///         column, and (2) set operations where on side is typed and the other is untyped.
    ///     </para>
    ///     <para>
    ///         Note that this visitor follows type columns across subquery projections. That is, if a root constant/parameter is buried
    ///         within subqueries, and somewhere above the column projected out of a subquery is inferred, this is picked up and propagated
    ///         all the way down.
    ///     </para>
    ///     <para>
    ///         The visitor does not change the query tree in any way - it only populates the inferred type mappings it identified in
    ///         the given dictionary; actual application of the inferred type mappings happens later in
    ///         <see cref="RelationalTypeMappingPostprocessor" />. We can't do this in a single pass since untyped roots