Simon Peyton Jones pushed to branch wip/T23162-spj at Glasgow Haskell Compiler / GHC
Commits:
-
a381c3c4
by Simon Peyton Jones at 2025-08-07T17:32:31+01:00
5 changed files:
- compiler/GHC/Tc/Solver/Dict.hs
- compiler/GHC/Tc/Solver/FunDeps.hs
- compiler/GHC/Tc/Solver/Monad.hs
- compiler/GHC/Tc/Solver/Solve.hs
- testsuite/tests/typecheck/should_fail/tcfail143.stderr
Changes:
... | ... | @@ -648,69 +648,6 @@ on whether we apply this optimization when IncoherentInstances is in effect: |
648 | 648 | The output of `main` if we avoid the optimization under the effect of
|
649 | 649 | IncoherentInstances is `1`. If we were to do the optimization, the output of
|
650 | 650 | `main` would be `2`.
|
651 | - |
|
652 | - |
|
653 | -Note [No Given/Given fundeps]
|
|
654 | -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
655 | -We do not create constraints from:
|
|
656 | -* Given/Given interactions via functional dependencies or type family
|
|
657 | - injectivity annotations.
|
|
658 | -* Given/instance fundep interactions via functional dependencies or
|
|
659 | - type family injectivity annotations.
|
|
660 | - |
|
661 | -In this Note, all these interactions are called just "fundeps".
|
|
662 | - |
|
663 | -We ingore such fundeps for several reasons:
|
|
664 | - |
|
665 | -1. These fundeps will never serve a purpose in accepting more
|
|
666 | - programs: Given constraints do not contain metavariables that could
|
|
667 | - be unified via exploring fundeps. They *could* be useful in
|
|
668 | - discovering inaccessible code. However, the constraints will be
|
|
669 | - Wanteds, and as such will cause errors (not just warnings) if they
|
|
670 | - go unsolved. Maybe there is a clever way to get the right
|
|
671 | - inaccessible code warnings, but the path forward is far from
|
|
672 | - clear. #12466 has further commentary.
|
|
673 | - |
|
674 | -2. Furthermore, here is a case where a Given/instance interaction is actively
|
|
675 | - harmful (from dependent/should_compile/RaeJobTalk):
|
|
676 | - |
|
677 | - type family a == b :: Bool
|
|
678 | - type family Not a = r | r -> a where
|
|
679 | - Not False = True
|
|
680 | - Not True = False
|
|
681 | - |
|
682 | - [G] Not (a == b) ~ True
|
|
683 | - |
|
684 | - Reacting this Given with the equations for Not produces
|
|
685 | - |
|
686 | - [W] a == b ~ False
|
|
687 | - |
|
688 | - This is indeed a true consequence, and would make sense as a fresh Given.
|
|
689 | - But we don't have a way to produce evidence for fundeps, as a Wanted it
|
|
690 | - is /harmful/: we can't prove it, and so we'll report an error and reject
|
|
691 | - the program. (Previously fundeps gave rise to Deriveds, which
|
|
692 | - carried no evidence, so it didn't matter that they could not be proved.)
|
|
693 | - |
|
694 | -3. #20922 showed a subtle different problem with Given/instance fundeps.
|
|
695 | - type family ZipCons (as :: [k]) (bssx :: [[k]]) = (r :: [[k]]) | r -> as bssx where
|
|
696 | - ZipCons (a ': as) (bs ': bss) = (a ': bs) ': ZipCons as bss
|
|
697 | - ...
|
|
698 | - |
|
699 | - tclevel = 4
|
|
700 | - [G] ZipCons is1 iss ~ (i : is2) : jss
|
|
701 | - |
|
702 | - (The tclevel=4 means that this Given is at level 4.) The fundep tells us that
|
|
703 | - 'iss' must be of form (is2 : beta[4]) where beta[4] is a fresh unification
|
|
704 | - variable; we don't know what type it stands for. So we would emit
|
|
705 | - [W] iss ~ is2 : beta
|
|
706 | - |
|
707 | - Again we can't prove that equality; and worse we'll rewrite iss to
|
|
708 | - (is2:beta) in deeply nested constraints inside this implication,
|
|
709 | - where beta is untouchable (under other equality constraints), leading
|
|
710 | - to other insoluble constraints.
|
|
711 | - |
|
712 | -The bottom line: since we have no evidence for them, we should ignore Given/Given
|
|
713 | -and Given/instance fundeps entirely.
|
|
714 | 651 | -}
|
715 | 652 | |
716 | 653 | tryInertDicts :: DictCt -> SolverStage ()
|
... | ... | @@ -313,7 +313,13 @@ doDictFunDepImprovement dict_ct |
313 | 313 | doDictFunDepImprovementLocal :: DictCt -> SolverStage ()
|
314 | 314 | -- Using functional dependencies, interact the DictCt with the
|
315 | 315 | -- inert Givens and Wanteds, to produce new equalities
|
316 | -doDictFunDepImprovementLocal dict_ct@(DictCt { di_cls = cls, di_ev = wanted_ev })
|
|
316 | +doDictFunDepImprovementLocal dict_ct@(DictCt { di_cls = cls, di_ev = work_ev })
|
|
317 | + | isGiven work_ev
|
|
318 | + = -- If work_ev is Given, there could in principle be some inert Wanteds
|
|
319 | + -- but in practice there never are because we solve Givens first
|
|
320 | + nopStage ()
|
|
321 | + |
|
322 | + | otherwise
|
|
317 | 323 | = Stage $
|
318 | 324 | do { inerts <- getInertCans
|
319 | 325 | |
... | ... | @@ -324,35 +330,44 @@ doDictFunDepImprovementLocal dict_ct@(DictCt { di_cls = cls, di_ev = wanted_ev } |
324 | 330 | ; if imp then startAgainWith (CDictCan dict_ct)
|
325 | 331 | else continueWith () }
|
326 | 332 | where
|
327 | - wanted_pred = ctEvPred wanted_ev
|
|
328 | - wanted_loc = ctEvLoc wanted_ev
|
|
333 | + work_pred = ctEvPred work_ev
|
|
334 | + work_loc = ctEvLoc work_ev
|
|
335 | + work_is_given = isGiven work_ev
|
|
329 | 336 | |
330 | 337 | do_interaction :: Cts -> DictCt -> TcS Cts
|
331 | - do_interaction new_eqs1 (DictCt { di_ev = all_ev }) -- This can be Given or Wanted
|
|
338 | + do_interaction new_eqs1 (DictCt { di_ev = inert_ev }) -- This can be Given or Wanted
|
|
339 | + | work_is_given && isGiven inert_ev
|
|
340 | + -- Do not create FDs from Given/Given interactions
|
|
341 | + -- See Note [No Given/Given fundeps]
|
|
342 | + -- It is possible for work_ev to be Given when inert_ev is Wanted:
|
|
343 | + -- this can happen if a Given is kicked out by a unification
|
|
344 | + = return new_eqs1
|
|
345 | + |
|
346 | + | otherwise
|
|
332 | 347 | = do { traceTcS "doLocalFunDepImprovement" $
|
333 | - vcat [ ppr wanted_ev
|
|
334 | - , pprCtLoc wanted_loc, ppr (isGivenLoc wanted_loc)
|
|
335 | - , pprCtLoc all_loc, ppr (isGivenLoc all_loc)
|
|
348 | + vcat [ ppr work_ev
|
|
349 | + , pprCtLoc work_loc, ppr (isGivenLoc work_loc)
|
|
350 | + , pprCtLoc inert_loc, ppr (isGivenLoc inert_loc)
|
|
336 | 351 | , pprCtLoc deriv_loc, ppr (isGivenLoc deriv_loc) ]
|
337 | 352 | |
338 | - ; new_eqs2 <- unifyFunDepWanteds_new wanted_ev $
|
|
339 | - improveFromAnother (deriv_loc, all_rewriters)
|
|
340 | - all_pred wanted_pred
|
|
353 | + ; new_eqs2 <- unifyFunDepWanteds_new work_ev $
|
|
354 | + improveFromAnother (deriv_loc, inert_rewriters)
|
|
355 | + inert_pred work_pred
|
|
341 | 356 | ; return (new_eqs1 `unionBags` new_eqs2) }
|
342 | 357 | where
|
343 | - all_pred = ctEvPred all_ev
|
|
344 | - all_loc = ctEvLoc all_ev
|
|
345 | - all_rewriters = ctEvRewriters all_ev
|
|
346 | - deriv_loc = wanted_loc { ctl_depth = deriv_depth
|
|
358 | + inert_pred = ctEvPred inert_ev
|
|
359 | + inert_loc = ctEvLoc inert_ev
|
|
360 | + inert_rewriters = ctEvRewriters inert_ev
|
|
361 | + deriv_loc = work_loc { ctl_depth = deriv_depth
|
|
347 | 362 | , ctl_origin = deriv_origin }
|
348 | - deriv_depth = ctl_depth wanted_loc `maxSubGoalDepth`
|
|
349 | - ctl_depth all_loc
|
|
350 | - deriv_origin = FunDepOrigin1 wanted_pred
|
|
351 | - (ctLocOrigin wanted_loc)
|
|
352 | - (ctLocSpan wanted_loc)
|
|
353 | - all_pred
|
|
354 | - (ctLocOrigin all_loc)
|
|
355 | - (ctLocSpan all_loc)
|
|
363 | + deriv_depth = ctl_depth work_loc `maxSubGoalDepth`
|
|
364 | + ctl_depth inert_loc
|
|
365 | + deriv_origin = FunDepOrigin1 work_pred
|
|
366 | + (ctLocOrigin work_loc)
|
|
367 | + (ctLocSpan work_loc)
|
|
368 | + inert_pred
|
|
369 | + (ctLocOrigin inert_loc)
|
|
370 | + (ctLocSpan inert_loc)
|
|
356 | 371 | |
357 | 372 | doDictFunDepImprovementTop :: DictCt -> SolverStage ()
|
358 | 373 | doDictFunDepImprovementTop dict_ct@(DictCt { di_ev = ev, di_cls = cls, di_tys = xis })
|
... | ... | @@ -389,6 +404,69 @@ solveFunDeps generate_eqs |
389 | 404 | do { eqs <- generate_eqs
|
390 | 405 | ; solveSimpleWanteds eqs }
|
391 | 406 | |
407 | +{- Note [No Given/Given fundeps]
|
|
408 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
409 | +We do not create constraints from:
|
|
410 | +* Given/Given interactions via functional dependencies or type family
|
|
411 | + injectivity annotations.
|
|
412 | +* Given/instance fundep interactions via functional dependencies or
|
|
413 | + type family injectivity annotations.
|
|
414 | + |
|
415 | +In this Note, all these interactions are called just "fundeps".
|
|
416 | + |
|
417 | +We ingore such fundeps for several reasons:
|
|
418 | + |
|
419 | +1. These fundeps will never serve a purpose in accepting more
|
|
420 | + programs: Given constraints do not contain metavariables that could
|
|
421 | + be unified via exploring fundeps. They *could* be useful in
|
|
422 | + discovering inaccessible code. However, the constraints will be
|
|
423 | + Wanteds, and as such will cause errors (not just warnings) if they
|
|
424 | + go unsolved. Maybe there is a clever way to get the right
|
|
425 | + inaccessible code warnings, but the path forward is far from
|
|
426 | + clear. #12466 has further commentary.
|
|
427 | + |
|
428 | +2. Furthermore, here is a case where a Given/instance interaction is actively
|
|
429 | + harmful (from dependent/should_compile/RaeJobTalk):
|
|
430 | + |
|
431 | + type family a == b :: Bool
|
|
432 | + type family Not a = r | r -> a where
|
|
433 | + Not False = True
|
|
434 | + Not True = False
|
|
435 | + |
|
436 | + [G] Not (a == b) ~ True
|
|
437 | + |
|
438 | + Reacting this Given with the equations for Not produces
|
|
439 | + |
|
440 | + [W] a == b ~ False
|
|
441 | + |
|
442 | + This is indeed a true consequence, and would make sense as a fresh Given.
|
|
443 | + But we don't have a way to produce evidence for fundeps, as a Wanted it
|
|
444 | + is /harmful/: we can't prove it, and so we'll report an error and reject
|
|
445 | + the program. (Previously fundeps gave rise to Deriveds, which
|
|
446 | + carried no evidence, so it didn't matter that they could not be proved.)
|
|
447 | + |
|
448 | +3. #20922 showed a subtle different problem with Given/instance fundeps.
|
|
449 | + type family ZipCons (as :: [k]) (bssx :: [[k]]) = (r :: [[k]]) | r -> as bssx where
|
|
450 | + ZipCons (a ': as) (bs ': bss) = (a ': bs) ': ZipCons as bss
|
|
451 | + ...
|
|
452 | + |
|
453 | + tclevel = 4
|
|
454 | + [G] ZipCons is1 iss ~ (i : is2) : jss
|
|
455 | + |
|
456 | + (The tclevel=4 means that this Given is at level 4.) The fundep tells us that
|
|
457 | + 'iss' must be of form (is2 : beta[4]) where beta[4] is a fresh unification
|
|
458 | + variable; we don't know what type it stands for. So we would emit
|
|
459 | + [W] iss ~ is2 : beta
|
|
460 | + |
|
461 | + Again we can't prove that equality; and worse we'll rewrite iss to
|
|
462 | + (is2:beta) in deeply nested constraints inside this implication,
|
|
463 | + where beta is untouchable (under other equality constraints), leading
|
|
464 | + to other insoluble constraints.
|
|
465 | + |
|
466 | +The bottom line: since we have no evidence for them, we should ignore Given/Given
|
|
467 | +and Given/instance fundeps entirely.
|
|
468 | +-}
|
|
469 | + |
|
392 | 470 | {-
|
393 | 471 | ************************************************************************
|
394 | 472 | * *
|
... | ... | @@ -1298,21 +1298,25 @@ nestFunDepsTcS (TcS thing_inside) |
1298 | 1298 | , tcs_worklist = new_wl_var
|
1299 | 1299 | , tcs_unif_lvl = new_unif_lvl_var }
|
1300 | 1300 | |
1301 | + ; TcM.traceTc "nestFunDepsTcS {" empty
|
|
1301 | 1302 | ; (inner_lvl, _res) <- TcM.pushTcLevelM $
|
1302 | 1303 | thing_inside nest_env
|
1304 | + -- Increase the level so that unification variables allocated by
|
|
1305 | + -- the fundep-creation itself don't count as useful unifications
|
|
1306 | + ; TcM.traceTc "nestFunDepsTcS }" empty
|
|
1303 | 1307 | |
1304 | 1308 | -- Figure out whether the fundeps did any useful unifications,
|
1305 | 1309 | -- and if so update the tcs_unif_lvl
|
1306 | 1310 | ; mb_new_lvl <- TcM.readTcRef new_unif_lvl_var
|
1307 | 1311 | ; case mb_new_lvl of
|
1308 | - Just new_lvl
|
|
1309 | - | inner_lvl `deeperThanOrSame` new_lvl
|
|
1312 | + Just unif_lvl
|
|
1313 | + | inner_lvl `deeperThanOrSame` unif_lvl
|
|
1310 | 1314 | -> -- Some useful unifications took place
|
1311 | 1315 | do { mb_old_lvl <- TcM.readTcRef unif_lvl_var
|
1312 | 1316 | ; case mb_old_lvl of
|
1313 | - Just old_lvl | new_lvl `deeperThanOrSame` old_lvl
|
|
1317 | + Just old_lvl | unif_lvl `deeperThanOrSame` old_lvl
|
|
1314 | 1318 | -> return ()
|
1315 | - _ -> TcM.writeTcRef unif_lvl_var (Just new_lvl)
|
|
1319 | + _ -> TcM.writeTcRef unif_lvl_var (Just unif_lvl)
|
|
1316 | 1320 | ; return True }
|
1317 | 1321 | |
1318 | 1322 | _ -> return False -- No unifications (except of vars
|
... | ... | @@ -126,7 +126,7 @@ simplify_loop n limit definitely_redo_implications |
126 | 126 | -- See Note [Rewrite insolubles] in GHC.Tc.Solver.InertSet
|
127 | 127 | |
128 | 128 | ; wc2 <- if not definitely_redo_implications -- See Note [Superclass iteration]
|
129 | - && n_unifs1 == 0 -- for this conditional
|
|
129 | + && n_unifs1 == 0 -- for this conditional
|
|
130 | 130 | then return (wc { wc_simple = simples1 }) -- Short cut
|
131 | 131 | else do { implics1 <- solveNestedImplications implics
|
132 | 132 | ; return (wc { wc_simple = simples1
|
1 | - |
|
2 | -tcfail143.hs:30:9: error: [GHC-18872]
|
|
3 | - • Couldn't match type ‘S Z’ with ‘Z’
|
|
4 | - arising from a functional dependency between:
|
|
5 | - constraint ‘MinMax (S Z) Z Z Z’ arising from a use of ‘extend’
|
|
6 | - instance ‘MinMax a Z Z a’ at tcfail143.hs:12:10-23
|
|
1 | +tcfail143.hs:30:9: error: [GHC-39999]
|
|
2 | + • No instance for ‘MinMax (S Z) Z Z Z’
|
|
3 | + arising from a use of ‘extend’
|
|
7 | 4 | • In the expression: n1 `extend` n0
|
8 | 5 | In an equation for ‘t2’: t2 = n1 `extend` n0
|
6 | + |