@@ -3504,3 +3504,193 @@ let ``Test ProjectForWitnesses4 GetWitnessPassingInfo`` () =
35043504 printfn "actual:\n\n%A" actual
35053505 actual
35063506 |> shouldEqual expected
3507+
3508+ //---------------------------------------------------------------------------------------------------------
3509+ // Regression tests for ImmediateSubExpressions on generic types with conditional comparison/equality
3510+ // https://github.com/dotnet/fsharp/issues/19118
3511+ //
3512+ // The bug: FCS crashes when accessing ImmediateSubExpressions on auto-generated comparison code
3513+ // for generic DUs/records whose type parameters have ComparisonConditionalOn but not actual
3514+ // comparison constraints. This is because GetWitnessArgs tries to generate witnesses for the
3515+ // comparison constraint, but fails because the type parameter is rigid and can't have constraints added.
3516+
3517+ module internal ProjectForWitnessConditionalComparison =
3518+
3519+ /// Helper to walk ALL expressions in a file, including ImmediateSubExpressions
3520+ /// This triggers the bug because it forces conversion of auto-generated comparison code
3521+ let walkAllExpressions (source : string) =
3522+ let fileName1 = System.IO.Path.ChangeExtension(getTemporaryFileName (), ".fs")
3523+ try
3524+ FileSystem.OpenFileForWriteShim(fileName1).Write(source)
3525+ let options = createProjectOptions [source] []
3526+ let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=CompilerAssertHelpers.UseTransparentCompiler)
3527+ let wholeProjectResults = exprChecker.ParseAndCheckProject(options) |> Async.RunImmediate
3528+
3529+ if wholeProjectResults.Diagnostics.Length > 0 then
3530+ for diag in wholeProjectResults.Diagnostics do
3531+ printfn "Diagnostic: %s" diag.Message
3532+
3533+ for implFile in wholeProjectResults.AssemblyContents.ImplementationFiles do
3534+ // Walk all declarations and their expressions, including ImmediateSubExpressions
3535+ let rec walkExpr (e: FSharpExpr) =
3536+ // Access ImmediateSubExpressions - this is what triggered #19118
3537+ for subExpr in e.ImmediateSubExpressions do
3538+ walkExpr subExpr
3539+
3540+ let rec walkDecl d =
3541+ match d with
3542+ | FSharpImplementationFileDeclaration.Entity (_, subDecls) ->
3543+ for subDecl in subDecls do
3544+ walkDecl subDecl
3545+ | FSharpImplementationFileDeclaration.MemberOrFunctionOrValue (_, _, e) ->
3546+ walkExpr e
3547+ | FSharpImplementationFileDeclaration.InitAction e ->
3548+ walkExpr e
3549+
3550+ for decl in implFile.Declarations do
3551+ walkDecl decl
3552+ finally
3553+ try
3554+ FileSystem.FileDeleteShim fileName1
3555+ with
3556+ | _ -> ()
3557+
3558+ [<Fact>]
3559+ let ``ImmediateSubExpressions - generic DU with no constraints should not crash`` () =
3560+ // This is the core bug repro - a generic DU where the type parameter has
3561+ // ComparisonConditionalOn but no actual comparison constraint
3562+ let source = """
3563+ module M
3564+
3565+ type Bar<'appEvent> =
3566+ | Wibble of 'appEvent
3567+ """
3568+ // This should not throw. Before the fix, it crashed with ConstraintSolverMissingConstraint.
3569+ ProjectForWitnessConditionalComparison.walkAllExpressions source
3570+
3571+ [<Fact>]
3572+ let ``ImmediateSubExpressions - generic DU with multiple type parameters should not crash`` () =
3573+ let source = """
3574+ module M
3575+
3576+ type MultiParam<'a, 'b, 'c> =
3577+ | Case1 of 'a
3578+ | Case2 of 'b * 'c
3579+ | Case3 of 'a * 'b * 'c
3580+ """
3581+ ProjectForWitnessConditionalComparison.walkAllExpressions source
3582+
3583+ [<Fact>]
3584+ let ``ImmediateSubExpressions - generic record with no constraints should not crash`` () =
3585+ let source = """
3586+ module M
3587+
3588+ type MyRecord<'t> = { Value: 't; Name: string }
3589+ """
3590+ ProjectForWitnessConditionalComparison.walkAllExpressions source
3591+
3592+ [<Fact>]
3593+ let ``ImmediateSubExpressions - generic struct DU should not crash`` () =
3594+ let source = """
3595+ module M
3596+
3597+ [<Struct>]
3598+ type StructDU<'a> =
3599+ | StructCase of value: 'a
3600+ """
3601+ ProjectForWitnessConditionalComparison.walkAllExpressions source
3602+
3603+ [<Fact>]
3604+ let ``ImmediateSubExpressions - nested generic types should not crash`` () =
3605+ let source = """
3606+ module M
3607+
3608+ type Outer<'a> =
3609+ | OuterCase of Inner<'a>
3610+
3611+ and Inner<'b> =
3612+ | InnerCase of 'b
3613+ """
3614+ ProjectForWitnessConditionalComparison.walkAllExpressions source
3615+
3616+ [<Fact>]
3617+ let ``ImmediateSubExpressions - generic DU with explicit comparison constraint works`` () =
3618+ // When the type parameter has the comparison constraint, witness generation should work;
3619+ // no crash occurred even before the bug was fixed. This test is here for completeness.
3620+ let source = """
3621+ module M
3622+
3623+ type WithConstraint<'a when 'a : comparison> =
3624+ | Constrained of 'a
3625+ """
3626+ ProjectForWitnessConditionalComparison.walkAllExpressions source
3627+
3628+ [<Fact>]
3629+ let ``ImmediateSubExpressions - non-generic DU works`` () =
3630+ // Non-generic types always worked fine (no generics = no witness issues). This test is here for completeness.
3631+ let source = """
3632+ module M
3633+
3634+ type SimpleUnion =
3635+ | Case1 of int
3636+ | Case2 of string
3637+ """
3638+ ProjectForWitnessConditionalComparison.walkAllExpressions source
3639+
3640+ [<Fact>]
3641+ let ``ImmediateSubExpressions - generic DU with NoComparison attribute should not crash`` () =
3642+ // With NoComparison, no comparison code is generated, so no crash ever occurred even before the bug was fixed.
3643+ // This test is here for completeness.
3644+ let source = """
3645+ module M
3646+
3647+ [<NoComparison>]
3648+ type NoCompare<'a> =
3649+ | NoCompareCase of 'a
3650+ """
3651+ ProjectForWitnessConditionalComparison.walkAllExpressions source
3652+
3653+ [<Fact>]
3654+ let ``ImmediateSubExpressions - generic DU with NoEquality attribute should not crash`` () =
3655+ let source = """
3656+ module M
3657+
3658+ [<NoEquality; NoComparison>]
3659+ type NoEq<'a> =
3660+ | NoEqCase of 'a
3661+ """
3662+ ProjectForWitnessConditionalComparison.walkAllExpressions source
3663+
3664+ [<Fact>]
3665+ let ``ImmediateSubExpressions - generic DU used in function should not crash`` () =
3666+ // Test that using the generic DU in actual code still works
3667+ let source = """
3668+ module M
3669+
3670+ type Option2<'t> =
3671+ | Some2 of 't
3672+ | None2
3673+
3674+ let mapOption2 f opt =
3675+ match opt with
3676+ | Some2 x -> Some2 (f x)
3677+ | None2 -> None2
3678+ """
3679+ ProjectForWitnessConditionalComparison.walkAllExpressions source
3680+
3681+ [<Fact>]
3682+ let ``ImmediateSubExpressions - complex generic type hierarchy should not crash`` () =
3683+ let source = """
3684+ module M
3685+
3686+ type Result<'ok, 'err> =
3687+ | Ok of 'ok
3688+ | Error of 'err
3689+
3690+ type Validated<'a> = Result<'a, string list>
3691+
3692+ let validate pred msg value : Validated<'a> =
3693+ if pred value then Ok value
3694+ else Error [msg]
3695+ """
3696+ ProjectForWitnessConditionalComparison.walkAllExpressions source
0 commit comments