From b8bcfcc463d28f32e5b4b52db5961c2145db97b9 Mon Sep 17 00:00:00 2001 From: Smaug123 <3138005+Smaug123@users.noreply.github.com> Date: Mon, 26 Jan 2026 19:59:46 +0000 Subject: [PATCH 1/3] Add failing tests --- .../ExprTests.fs | 503 ++++++++++++++++++ 1 file changed, 503 insertions(+) diff --git a/tests/FSharp.Compiler.Service.Tests/ExprTests.fs b/tests/FSharp.Compiler.Service.Tests/ExprTests.fs index be97d17617d..38833834f2d 100644 --- a/tests/FSharp.Compiler.Service.Tests/ExprTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/ExprTests.fs @@ -3694,3 +3694,506 @@ let validate pred msg value : Validated<'a> = else Error [msg] """ ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_tuple - DU case with tuple containing generic param`` () = + // The tuple ('a * int) creates a TType_tuple. hasConditionalTypar must recurse into it. + let source = """ +module M + +type WithTuple<'a> = + | TupleCase of 'a * int +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_tuple - DU case with nested tuple containing generic param`` () = + // Nested tuples: ('a * (int * string)) + let source = """ +module M + +type WithNestedTuple<'a> = + | NestedTupleCase of 'a * (int * string) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_tuple - DU case with generic param in second position`` () = + // (int * 'a) - generic param not in first position + let source = """ +module M + +type TupleSecondPos<'a> = + | Case of int * 'a +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_tuple - DU case with multiple generics in tuple`` () = + // ('a * 'b * 'c) - multiple generic params in tuple + let source = """ +module M + +type MultiGenericTuple<'a, 'b, 'c> = + | Case of 'a * 'b * 'c +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_tuple - struct tuple containing generic param`` () = + // struct ('a * int) creates a struct TType_tuple + let source = """ +module M + +type WithStructTuple<'a> = + | StructTupleCase of struct ('a * int) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_tuple - record with tuple field containing generic`` () = + let source = """ +module M + +type RecordWithTuple<'a> = { Pair: 'a * int; Name: string } +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_fun - DU case with function type containing generic param in domain`` () = + // ('a -> int) creates a TType_fun with generic in domain position + let source = """ +module M + +type WithFunDomain<'a> = + | FunCase of ('a -> int) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_fun - DU case with function type containing generic param in range`` () = + // (int -> 'a) creates a TType_fun with generic in range position + let source = """ +module M + +type WithFunRange<'a> = + | FunCase of (int -> 'a) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_fun - DU case with function type containing generic in both positions`` () = + // ('a -> 'b) creates a TType_fun with generics in both positions + let source = """ +module M + +type WithFunBoth<'a, 'b> = + | FunCase of ('a -> 'b) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_fun - DU case with nested function type`` () = + // ('a -> int -> string) - curried function + let source = """ +module M + +type WithNestedFun<'a> = + | FunCase of ('a -> int -> string) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_fun - DU case with higher-order function`` () = + // (('a -> int) -> string) - function taking a function + let source = """ +module M + +type WithHigherOrderFun<'a> = + | FunCase of (('a -> int) -> string) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_fun - record with function field`` () = + let source = """ +module M + +type RecordWithFun<'a> = { Transform: 'a -> string; Id: int } +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_anon - DU case with anonymous record containing generic param`` () = + // {| Value: 'a |} creates a TType_anon + let source = """ +module M + +type WithAnon<'a> = + | AnonCase of {| Value: 'a |} +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_anon - DU case with anonymous record multiple fields`` () = + // {| First: 'a; Second: 'b |} with multiple generic params + let source = """ +module M + +type WithAnonMultiple<'a, 'b> = + | AnonCase of {| First: 'a; Second: 'b; Fixed: int |} +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_anon - DU case with nested anonymous record`` () = + // {| Inner: {| Value: 'a |} |} - nested anon records + let source = """ +module M + +type WithNestedAnon<'a> = + | AnonCase of {| Inner: {| Value: 'a |} |} +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_anon - struct anonymous record containing generic`` () = + let source = """ +module M + +type WithStructAnon<'a> = + | StructAnonCase of struct {| Value: 'a |} +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_anon - record with anonymous record field`` () = + let source = """ +module M + +type RecordWithAnon<'a> = { Anon: {| Data: 'a |}; Name: string } +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - mixed TType_tuple and TType_fun`` () = + // ('a * int) -> string - tuple in function domain + let source = """ +module M + +type MixedTupleFun<'a> = + | Case of (('a * int) -> string) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - mixed TType_fun in TType_tuple`` () = + // ('a -> int) * string - function in tuple + let source = """ +module M + +type MixedFunTuple<'a> = + | Case of (('a -> int) * string) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - mixed TType_anon in TType_tuple`` () = + // {| V: 'a |} * int - anon record in tuple + let source = """ +module M + +type MixedAnonTuple<'a> = + | Case of ({| V: 'a |} * int) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - mixed TType_tuple in TType_anon`` () = + // {| Pair: 'a * int |} - tuple in anon record + let source = """ +module M + +type MixedTupleAnon<'a> = + | Case of {| Pair: 'a * int |} +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - deeply nested combination`` () = + // Complex nesting of tuple, function, and anon record + let source = """ +module M + +type DeeplyNested<'a, 'b> = + | Case of {| Transform: ('a * int) -> {| Result: 'b |}; Pair: 'a * 'b |} +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - generic param wrapped in list inside tuple`` () = + // ('a list * int) - TType_app inside TType_tuple + let source = """ +module M + +type ListInTuple<'a> = + | Case of 'a list * int +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - generic param wrapped in option inside function`` () = + // ('a option -> int) - TType_app inside TType_fun + let source = """ +module M + +type OptionInFun<'a> = + | Case of ('a option -> int) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - generic param wrapped in array inside anon record`` () = + // {| Items: 'a array |} - TType_app inside TType_anon + let source = """ +module M + +type ArrayInAnon<'a> = + | Case of {| Items: 'a array |} +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - TType_ucase - DU containing another DU with generic`` () = + // Outer DU case contains an inner DU instantiated with the generic param + let source = """ +module M + +type Inner<'a> = + | InnerCase of 'a + +type Outer<'a> = + | OuterCase of Inner<'a> * int +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - triple nested generic in tuple`` () = + // ((('a * int) * string) * bool) - deeply nested tuple + let source = """ +module M + +type TripleNestedTuple<'a> = + | Case of ((('a * int) * string) * bool) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - generic at leaf of complex function type`` () = + // int -> string -> bool -> 'a - generic only at the end + let source = """ +module M + +type FunWithGenericAtEnd<'a> = + | Case of (int -> string -> bool -> 'a) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - struct DU with tuple case`` () = + let source = """ +module M + +[] +type StructWithTuple<'a> = + | Case of item1: 'a * item2: int +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - struct record with tuple field`` () = + let source = """ +module M + +[] +type StructRecordWithTuple<'a> = { Pair: 'a * int } +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - struct record with function field`` () = + let source = """ +module M + +[] +type StructRecordWithFun<'a> = { Transform: 'a -> int } +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - multiple DU cases with different tuple shapes`` () = + let source = """ +module M + +type MultiCaseTuples<'a, 'b> = + | Case1 of 'a * int + | Case2 of int * 'b + | Case3 of 'a * 'b * string + | Case4 of ('a * int) * ('b * string) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - DU with both explicit constraint and tuple`` () = + // One param has comparison, one doesn't; both appear in tuples + let source = """ +module M + +type MixedConstraintsTuple<'a, 'b when 'a : comparison> = + | Case of 'a * 'b * int +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - recursive DU with tuple`` () = + let source = """ +module M + +type Tree<'a> = + | Leaf of 'a + | Node of Tree<'a> * Tree<'a> +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - mutually recursive DUs with tuples`` () = + let source = """ +module M + +type Expr<'a> = + | Const of 'a + | Binary of Expr<'a> * Op<'a> * Expr<'a> + +and Op<'a> = + | Add + | Custom of ('a * 'a -> 'a) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - generic class with method returning tuple`` () = + let source = """ +module M + +type Container<'a>(value: 'a) = + member _.GetWithIndex(i: int) : 'a * int = (value, i) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - generic interface implementation in object expression with tuple`` () = + let source = """ +module M + +type IPair<'a> = + abstract GetPair: unit -> 'a * int + +let makePair (x: 'a) = + { new IPair<'a> with + member _.GetPair() = (x, 42) } +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - Choice type with tuple`` () = + // F# Choice types are common and may trigger the bug + let source = """ +module M + +type MyChoice<'a, 'b> = + | Choice1 of 'a * int + | Choice2 of 'b * string +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - Result-like type with tuple in error case`` () = + let source = """ +module M + +type Outcome<'ok, 'err> = + | Success of 'ok + | Failure of 'err * string // error with message +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - async-like wrapper with tuple`` () = + let source = """ +module M + +type Delayed<'a> = + | Immediate of 'a + | Deferred of (unit -> 'a) * int // thunk with delay +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - event type with handler function`` () = + let source = """ +module M + +type Event<'a> = + | Event of handler: ('a -> unit) * name: string +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - validation type with function`` () = + let source = """ +module M + +type Validator<'a> = + | Validator of validate: ('a -> bool) * errorMsg: string +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - codec type with encode and decode functions`` () = + let source = """ +module M + +type Codec<'a> = + | Codec of encode: ('a -> string) * decode: (string -> 'a option) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - state monad-like type`` () = + let source = """ +module M + +type State<'s, 'a> = + | State of run: ('s -> 'a * 's) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - reader monad-like type`` () = + let source = """ +module M + +type Reader<'env, 'a> = + | Reader of run: ('env -> 'a) +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source + +[] +let ``ImmediateSubExpressions - writer monad-like type with tuple`` () = + let source = """ +module M + +type Writer<'w, 'a> = + | Writer of value: 'a * log: 'w list +""" + ProjectForWitnessConditionalComparison.walkAllExpressions source From b786740928e60551841698074750d48692639e43 Mon Sep 17 00:00:00 2001 From: Smaug123 <3138005+Smaug123@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:03:28 +0000 Subject: [PATCH 2/3] Handle all cases of TType --- src/Compiler/Symbols/Exprs.fs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Compiler/Symbols/Exprs.fs b/src/Compiler/Symbols/Exprs.fs index 3a514db3df7..4281393e5e8 100644 --- a/src/Compiler/Symbols/Exprs.fs +++ b/src/Compiler/Symbols/Exprs.fs @@ -520,8 +520,13 @@ module FSharpExprConvert = let rec hasConditionalTypar ty = match stripTyEqns g ty with | TType_var (tp, _) -> tp.ComparisonConditionalOn || tp.EqualityConditionalOn - | TType_app (_, tinst, _) -> tinst |> List.exists hasConditionalTypar - | _ -> false + | TType_app (_, tinst, _) + | TType_ucase (_, tinst) + | TType_anon (_, tinst) + | TType_tuple (_, tinst) -> tinst |> List.exists hasConditionalTypar + | TType_fun (domainTy, rangeTy, _) -> hasConditionalTypar domainTy || hasConditionalTypar rangeTy + | TType_forall (_, bodyTy) -> hasConditionalTypar bodyTy + | TType_measure _ -> false let witnessExprs = match ConstraintSolver.CodegenWitnessesForTyparInst cenv.tcValF g cenv.amap m tps tyargs with From 733f90c2c0451cebf46ce5b0c437bd98218e4fcb Mon Sep 17 00:00:00 2001 From: Smaug123 <3138005+Smaug123@users.noreply.github.com> Date: Mon, 26 Jan 2026 20:12:26 +0000 Subject: [PATCH 3/3] Parameterise the tests --- .../ExprTests.fs | 566 ++++++------------ 1 file changed, 188 insertions(+), 378 deletions(-) diff --git a/tests/FSharp.Compiler.Service.Tests/ExprTests.fs b/tests/FSharp.Compiler.Service.Tests/ExprTests.fs index 38833834f2d..f628870119a 100644 --- a/tests/FSharp.Compiler.Service.Tests/ExprTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/ExprTests.fs @@ -3555,54 +3555,44 @@ module internal ProjectForWitnessConditionalComparison = with | _ -> () -[] -let ``ImmediateSubExpressions - generic DU with no constraints should not crash`` () = - // This is the core bug repro - a generic DU where the type parameter has - // ComparisonConditionalOn but no actual comparison constraint - let source = """ +/// Test case for ImmediateSubExpressions tests. ToString() returns just the name for test runner display. +type ImmediateSubExpressionsTestCase = + { Name: string; Source: string } + override this.ToString() = this.Name + +let immediateSubExpressionsTestCases: obj[][] = [| + [| { Name = "generic DU with no constraints should not crash" + Source = """ module M type Bar<'appEvent> = | Wibble of 'appEvent -""" - // This should not throw. Before the fix, it crashed with ConstraintSolverMissingConstraint. - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic DU with multiple type parameters should not crash`` () = - let source = """ +""" } |] + [| { Name = "generic DU with multiple type parameters should not crash" + Source = """ module M type MultiParam<'a, 'b, 'c> = | Case1 of 'a | Case2 of 'b * 'c | Case3 of 'a * 'b * 'c -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic record with no constraints should not crash`` () = - let source = """ +""" } |] + [| { Name = "generic record with no constraints should not crash" + Source = """ module M type MyRecord<'t> = { Value: 't; Name: string } -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic struct DU should not crash`` () = - let source = """ +""" } |] + [| { Name = "generic struct DU should not crash" + Source = """ module M [] type StructDU<'a> = | StructCase of value: 'a -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - nested generic types should not crash`` () = - let source = """ +""" } |] + [| { Name = "nested generic types should not crash" + Source = """ module M type Outer<'a> = @@ -3610,61 +3600,40 @@ type Outer<'a> = and Inner<'b> = | InnerCase of 'b -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic DU with explicit comparison constraint works`` () = - // When the type parameter has the comparison constraint, witness generation should work; - // no crash occurred even before the bug was fixed. This test is here for completeness. - let source = """ +""" } |] + [| { Name = "generic DU with explicit comparison constraint works" + Source = """ module M type WithConstraint<'a when 'a : comparison> = | Constrained of 'a -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - non-generic DU works`` () = - // Non-generic types always worked fine (no generics = no witness issues). This test is here for completeness. - let source = """ +""" } |] + [| { Name = "non-generic DU works" + Source = """ module M type SimpleUnion = | Case1 of int | Case2 of string -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic DU with NoComparison attribute should not crash`` () = - // With NoComparison, no comparison code is generated, so no crash ever occurred even before the bug was fixed. - // This test is here for completeness. - let source = """ +""" } |] + [| { Name = "generic DU with NoComparison attribute should not crash" + Source = """ module M [] type NoCompare<'a> = | NoCompareCase of 'a -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic DU with NoEquality attribute should not crash`` () = - let source = """ +""" } |] + [| { Name = "generic DU with NoEquality attribute should not crash" + Source = """ module M [] type NoEq<'a> = | NoEqCase of 'a -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic DU used in function should not crash`` () = - // Test that using the generic DU in actual code still works - let source = """ +""" } |] + [| { Name = "generic DU used in function should not crash" + Source = """ module M type Option2<'t> = @@ -3675,12 +3644,9 @@ let mapOption2 f opt = match opt with | Some2 x -> Some2 (f x) | None2 -> None2 -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - complex generic type hierarchy should not crash`` () = - let source = """ +""" } |] + [| { Name = "complex generic type hierarchy should not crash" + Source = """ module M type Result<'ok, 'err> = @@ -3692,281 +3658,181 @@ type Validated<'a> = Result<'a, string list> let validate pred msg value : Validated<'a> = if pred value then Ok value else Error [msg] -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_tuple - DU case with tuple containing generic param`` () = - // The tuple ('a * int) creates a TType_tuple. hasConditionalTypar must recurse into it. - let source = """ +""" } |] + [| { Name = "TType_tuple - DU case with tuple containing generic param" + Source = """ module M type WithTuple<'a> = | TupleCase of 'a * int -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_tuple - DU case with nested tuple containing generic param`` () = - // Nested tuples: ('a * (int * string)) - let source = """ +""" } |] + [| { Name = "TType_tuple - DU case with nested tuple containing generic param" + Source = """ module M type WithNestedTuple<'a> = | NestedTupleCase of 'a * (int * string) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_tuple - DU case with generic param in second position`` () = - // (int * 'a) - generic param not in first position - let source = """ +""" } |] + [| { Name = "TType_tuple - DU case with generic param in second position" + Source = """ module M type TupleSecondPos<'a> = | Case of int * 'a -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_tuple - DU case with multiple generics in tuple`` () = - // ('a * 'b * 'c) - multiple generic params in tuple - let source = """ +""" } |] + [| { Name = "TType_tuple - DU case with multiple generics in tuple" + Source = """ module M type MultiGenericTuple<'a, 'b, 'c> = | Case of 'a * 'b * 'c -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_tuple - struct tuple containing generic param`` () = - // struct ('a * int) creates a struct TType_tuple - let source = """ +""" } |] + [| { Name = "TType_tuple - struct tuple containing generic param" + Source = """ module M type WithStructTuple<'a> = | StructTupleCase of struct ('a * int) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_tuple - record with tuple field containing generic`` () = - let source = """ +""" } |] + [| { Name = "TType_tuple - record with tuple field containing generic" + Source = """ module M type RecordWithTuple<'a> = { Pair: 'a * int; Name: string } -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_fun - DU case with function type containing generic param in domain`` () = - // ('a -> int) creates a TType_fun with generic in domain position - let source = """ +""" } |] + [| { Name = "TType_fun - DU case with function type containing generic param in domain" + Source = """ module M type WithFunDomain<'a> = | FunCase of ('a -> int) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_fun - DU case with function type containing generic param in range`` () = - // (int -> 'a) creates a TType_fun with generic in range position - let source = """ +""" } |] + [| { Name = "TType_fun - DU case with function type containing generic param in range" + Source = """ module M type WithFunRange<'a> = | FunCase of (int -> 'a) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_fun - DU case with function type containing generic in both positions`` () = - // ('a -> 'b) creates a TType_fun with generics in both positions - let source = """ +""" } |] + [| { Name = "TType_fun - DU case with function type containing generic in both positions" + Source = """ module M type WithFunBoth<'a, 'b> = | FunCase of ('a -> 'b) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_fun - DU case with nested function type`` () = - // ('a -> int -> string) - curried function - let source = """ +""" } |] + [| { Name = "TType_fun - DU case with nested function type" + Source = """ module M type WithNestedFun<'a> = | FunCase of ('a -> int -> string) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_fun - DU case with higher-order function`` () = - // (('a -> int) -> string) - function taking a function - let source = """ +""" } |] + [| { Name = "TType_fun - DU case with higher-order function" + Source = """ module M type WithHigherOrderFun<'a> = | FunCase of (('a -> int) -> string) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_fun - record with function field`` () = - let source = """ +""" } |] + [| { Name = "TType_fun - record with function field" + Source = """ module M type RecordWithFun<'a> = { Transform: 'a -> string; Id: int } -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_anon - DU case with anonymous record containing generic param`` () = - // {| Value: 'a |} creates a TType_anon - let source = """ +""" } |] + [| { Name = "TType_anon - DU case with anonymous record containing generic param" + Source = """ module M type WithAnon<'a> = | AnonCase of {| Value: 'a |} -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_anon - DU case with anonymous record multiple fields`` () = - // {| First: 'a; Second: 'b |} with multiple generic params - let source = """ +""" } |] + [| { Name = "TType_anon - DU case with anonymous record multiple fields" + Source = """ module M type WithAnonMultiple<'a, 'b> = | AnonCase of {| First: 'a; Second: 'b; Fixed: int |} -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_anon - DU case with nested anonymous record`` () = - // {| Inner: {| Value: 'a |} |} - nested anon records - let source = """ +""" } |] + [| { Name = "TType_anon - DU case with nested anonymous record" + Source = """ module M type WithNestedAnon<'a> = | AnonCase of {| Inner: {| Value: 'a |} |} -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_anon - struct anonymous record containing generic`` () = - let source = """ +""" } |] + [| { Name = "TType_anon - struct anonymous record containing generic" + Source = """ module M type WithStructAnon<'a> = | StructAnonCase of struct {| Value: 'a |} -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_anon - record with anonymous record field`` () = - let source = """ +""" } |] + [| { Name = "TType_anon - record with anonymous record field" + Source = """ module M type RecordWithAnon<'a> = { Anon: {| Data: 'a |}; Name: string } -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - mixed TType_tuple and TType_fun`` () = - // ('a * int) -> string - tuple in function domain - let source = """ +""" } |] + [| { Name = "mixed TType_tuple and TType_fun" + Source = """ module M type MixedTupleFun<'a> = | Case of (('a * int) -> string) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - mixed TType_fun in TType_tuple`` () = - // ('a -> int) * string - function in tuple - let source = """ +""" } |] + [| { Name = "mixed TType_fun in TType_tuple" + Source = """ module M type MixedFunTuple<'a> = | Case of (('a -> int) * string) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - mixed TType_anon in TType_tuple`` () = - // {| V: 'a |} * int - anon record in tuple - let source = """ +""" } |] + [| { Name = "mixed TType_anon in TType_tuple" + Source = """ module M type MixedAnonTuple<'a> = | Case of ({| V: 'a |} * int) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - mixed TType_tuple in TType_anon`` () = - // {| Pair: 'a * int |} - tuple in anon record - let source = """ +""" } |] + [| { Name = "mixed TType_tuple in TType_anon" + Source = """ module M type MixedTupleAnon<'a> = | Case of {| Pair: 'a * int |} -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - deeply nested combination`` () = - // Complex nesting of tuple, function, and anon record - let source = """ +""" } |] + [| { Name = "deeply nested combination" + Source = """ module M type DeeplyNested<'a, 'b> = | Case of {| Transform: ('a * int) -> {| Result: 'b |}; Pair: 'a * 'b |} -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic param wrapped in list inside tuple`` () = - // ('a list * int) - TType_app inside TType_tuple - let source = """ +""" } |] + [| { Name = "generic param wrapped in list inside tuple" + Source = """ module M type ListInTuple<'a> = | Case of 'a list * int -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic param wrapped in option inside function`` () = - // ('a option -> int) - TType_app inside TType_fun - let source = """ +""" } |] + [| { Name = "generic param wrapped in option inside function" + Source = """ module M type OptionInFun<'a> = | Case of ('a option -> int) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic param wrapped in array inside anon record`` () = - // {| Items: 'a array |} - TType_app inside TType_anon - let source = """ +""" } |] + [| { Name = "generic param wrapped in array inside anon record" + Source = """ module M type ArrayInAnon<'a> = | Case of {| Items: 'a array |} -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - TType_ucase - DU containing another DU with generic`` () = - // Outer DU case contains an inner DU instantiated with the generic param - let source = """ +""" } |] + [| { Name = "TType_ucase - DU containing another DU with generic" + Source = """ module M type Inner<'a> = @@ -3974,65 +3840,45 @@ type Inner<'a> = type Outer<'a> = | OuterCase of Inner<'a> * int -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - triple nested generic in tuple`` () = - // ((('a * int) * string) * bool) - deeply nested tuple - let source = """ +""" } |] + [| { Name = "triple nested generic in tuple" + Source = """ module M type TripleNestedTuple<'a> = | Case of ((('a * int) * string) * bool) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic at leaf of complex function type`` () = - // int -> string -> bool -> 'a - generic only at the end - let source = """ +""" } |] + [| { Name = "generic at leaf of complex function type" + Source = """ module M type FunWithGenericAtEnd<'a> = | Case of (int -> string -> bool -> 'a) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - struct DU with tuple case`` () = - let source = """ +""" } |] + [| { Name = "struct DU with tuple case" + Source = """ module M [] type StructWithTuple<'a> = | Case of item1: 'a * item2: int -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - struct record with tuple field`` () = - let source = """ +""" } |] + [| { Name = "struct record with tuple field" + Source = """ module M [] type StructRecordWithTuple<'a> = { Pair: 'a * int } -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - struct record with function field`` () = - let source = """ +""" } |] + [| { Name = "struct record with function field" + Source = """ module M [] type StructRecordWithFun<'a> = { Transform: 'a -> int } -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - multiple DU cases with different tuple shapes`` () = - let source = """ +""" } |] + [| { Name = "multiple DU cases with different tuple shapes" + Source = """ module M type MultiCaseTuples<'a, 'b> = @@ -4040,34 +3886,24 @@ type MultiCaseTuples<'a, 'b> = | Case2 of int * 'b | Case3 of 'a * 'b * string | Case4 of ('a * int) * ('b * string) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - DU with both explicit constraint and tuple`` () = - // One param has comparison, one doesn't; both appear in tuples - let source = """ +""" } |] + [| { Name = "DU with both explicit constraint and tuple" + Source = """ module M type MixedConstraintsTuple<'a, 'b when 'a : comparison> = | Case of 'a * 'b * int -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - recursive DU with tuple`` () = - let source = """ +""" } |] + [| { Name = "recursive DU with tuple" + Source = """ module M type Tree<'a> = | Leaf of 'a | Node of Tree<'a> * Tree<'a> -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - mutually recursive DUs with tuples`` () = - let source = """ +""" } |] + [| { Name = "mutually recursive DUs with tuples" + Source = """ module M type Expr<'a> = @@ -4077,22 +3913,16 @@ type Expr<'a> = and Op<'a> = | Add | Custom of ('a * 'a -> 'a) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic class with method returning tuple`` () = - let source = """ +""" } |] + [| { Name = "generic class with method returning tuple" + Source = """ module M type Container<'a>(value: 'a) = member _.GetWithIndex(i: int) : 'a * int = (value, i) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - generic interface implementation in object expression with tuple`` () = - let source = """ +""" } |] + [| { Name = "generic interface implementation in object expression with tuple" + Source = """ module M type IPair<'a> = @@ -4101,99 +3931,79 @@ type IPair<'a> = let makePair (x: 'a) = { new IPair<'a> with member _.GetPair() = (x, 42) } -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - Choice type with tuple`` () = - // F# Choice types are common and may trigger the bug - let source = """ +""" } |] + [| { Name = "Choice type with tuple" + Source = """ module M type MyChoice<'a, 'b> = | Choice1 of 'a * int | Choice2 of 'b * string -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - Result-like type with tuple in error case`` () = - let source = """ +""" } |] + [| { Name = "Result-like type with tuple in error case" + Source = """ module M type Outcome<'ok, 'err> = | Success of 'ok - | Failure of 'err * string // error with message -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - async-like wrapper with tuple`` () = - let source = """ + | Failure of 'err * string +""" } |] + [| { Name = "async-like wrapper with tuple" + Source = """ module M type Delayed<'a> = | Immediate of 'a - | Deferred of (unit -> 'a) * int // thunk with delay -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - event type with handler function`` () = - let source = """ + | Deferred of (unit -> 'a) * int +""" } |] + [| { Name = "event type with handler function" + Source = """ module M type Event<'a> = | Event of handler: ('a -> unit) * name: string -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - validation type with function`` () = - let source = """ +""" } |] + [| { Name = "validation type with function" + Source = """ module M type Validator<'a> = | Validator of validate: ('a -> bool) * errorMsg: string -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - codec type with encode and decode functions`` () = - let source = """ +""" } |] + [| { Name = "codec type with encode and decode functions" + Source = """ module M type Codec<'a> = | Codec of encode: ('a -> string) * decode: (string -> 'a option) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - state monad-like type`` () = - let source = """ +""" } |] + [| { Name = "state monad-like type" + Source = """ module M type State<'s, 'a> = | State of run: ('s -> 'a * 's) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - reader monad-like type`` () = - let source = """ +""" } |] + [| { Name = "reader monad-like type" + Source = """ module M type Reader<'env, 'a> = | Reader of run: ('env -> 'a) -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source - -[] -let ``ImmediateSubExpressions - writer monad-like type with tuple`` () = - let source = """ +""" } |] + [| { Name = "writer monad-like type with tuple" + Source = """ module M type Writer<'w, 'a> = | Writer of value: 'a * log: 'w list -""" - ProjectForWitnessConditionalComparison.walkAllExpressions source +""" } |] +|] + +[] +[] +let ``ImmediateSubExpressions`` (case: ImmediateSubExpressionsTestCase) = + // These tests verify that FCS doesn't crash when accessing ImmediateSubExpressions + // on auto-generated comparison code for generic DUs/records. + // Before the fix for #19118, this would crash with ConstraintSolverMissingConstraint. + ProjectForWitnessConditionalComparison.walkAllExpressions case.Source