Header menu logo F# Compiler Guide

Breaking Changes in Query Expression Fixes

AnonymousObject Structural Equality 🔴

AnonymousObject<T> types now implement Equals/GetHashCode with structural equality.

Impact: Code using these as Dictionary keys with identity semantics will see different behavior.

let a = AnonymousObject(1, 2)
let b = AnonymousObject(1, 2)
a.Equals(b)  // Was: false, Now: true

Why: Fixes #7885, #47 - join/groupBy with tuple keys now work correctly.


Expression Tree Structure Changes 🟡

Let-bindings use Block instead of Lambda.Invoke

<@ let x = 1 in x + 1 @>
// Was: Lambda(x => x+1).Invoke(1)
// Now: Block({ x = 1; x + 1 })

Migration: If you detected let-bindings via Lambda.Invoke:

// Old: if (node.Method.Name == "Invoke" && node.Object is LambdaExpression)
// New: if (node.NodeType == ExpressionType.Block)

Array access uses ArrayIndex

<@ arr.[0] @>
// Was: Call(GetArray, arr, 0)
// Now: ArrayIndex(arr, 0)

Migration:

// Old: if (node.Method.Name == "GetArray")
// New: if (node.NodeType == ExpressionType.ArrayIndex)

IQueryable Type Preservation 🟡

Tuple projections now preserve the provider's IQueryable type.

query { for p in db.Products do select (p.Id, p.Name) }
// Was: EnumerableQuery<_>
// Now: Provider's IQueryable<_>

Impact: Code checking is EnumerableQuery<_> may need adjustment.


FCS API: Query Variables 🟢

Query variables now report IsCompilerGenerated = true. Fixes #422 (FS1182 false positives).

val a: obj
val b: obj
System.Object.Equals(obj: obj) : bool
val x: int
val query: Linq.QueryBuilder
val p: obj
custom operation: select ('Result) Calls Linq.QueryBuilder.Select

Type something to start searching.