Zubin pushed to branch wip/27627 at Glasgow Haskell Compiler / GHC
Commits:
-
9032bd3d
by Zubin Duggal at 2026-08-14T14:07:49+05:30
14 changed files:
- + changelog.d/27627
- compiler/GHC/Core.hs
- compiler/GHC/Core/DataCon.hs-boot
- compiler/GHC/Core/Lint.hs
- compiler/GHC/Core/Opt/WorkWrap/Utils.hs
- compiler/GHC/Core/TyCon.hs
- compiler/GHC/Core/Type.hs
- compiler/GHC/Core/Utils.hs
- compiler/GHC/Types/RepType.hs
- + testsuite/tests/core-to-stg/T27627/Callee.hs
- + testsuite/tests/core-to-stg/T27627/Caller.hs
- + testsuite/tests/core-to-stg/T27627/Main.hs
- + testsuite/tests/core-to-stg/T27627/T27627.stdout
- + testsuite/tests/core-to-stg/T27627/all.T
Changes:
| 1 | +section: compiler
|
|
| 2 | +synopsis: Fix a bug where an absent dictionary argument of a unary class could
|
|
| 3 | + be replaced by an error thunk, which GHC then evaluated, crashing the program.
|
|
| 4 | +mrs:
|
|
| 5 | +issues: #27627 |
| ... | ... | @@ -644,21 +644,20 @@ parts of the compilation pipeline. |
| 644 | 644 | |
| 645 | 645 | Note [NON-BOTTOM-DICTS invariant]
|
| 646 | 646 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 647 | -It is a global invariant (not checkable by Lint) that
|
|
| 647 | +It is a global invariant that
|
|
| 648 | 648 | |
| 649 | - every non-newtype dictionary-typed expression is non-bottom.
|
|
| 649 | + a dictionary-typed expression is never bottom,
|
|
| 650 | 650 | |
| 651 | -These conditions are captured by GHC.Core.Type.isTerminatingType.
|
|
| 651 | +The exception is unary classes; see (1) below.
|
|
| 652 | +GHC.Core.Type.isTerminatingType says which types the invariant covers.
|
|
| 652 | 653 | |
| 653 | -How are we so sure about this? Dictionaries are built by GHC in only two ways:
|
|
| 654 | +How are we so sure about this? GHC builds a dictionary in only two ways:
|
|
| 654 | 655 | |
| 655 | 656 | * A dictionary function (DFun), arising from an instance declaration.
|
| 656 | 657 | DFuns do no computation: they always return a data constructor immediately.
|
| 657 | 658 | See DFunUnfolding in GHC.Core. So the result of a call to a DFun is always
|
| 658 | 659 | non-bottom.
|
| 659 | 660 | |
| 660 | - Exception: newtype dictionaries.
|
|
| 661 | - |
|
| 662 | 661 | Plus: see the Very Nasty Wrinkle in Note [Speculative evaluation]
|
| 663 | 662 | in GHC.CoreToStg.Prep
|
| 664 | 663 | |
| ... | ... | @@ -666,20 +665,89 @@ How are we so sure about this? Dictionaries are built by GHC in only two ways: |
| 666 | 665 | see Note [Recursive superclasses] and Note [Solving superclass constraints]
|
| 667 | 666 | in GHC.Tc.TyCl.Instance.
|
| 668 | 667 | |
| 669 | -A bad Core-to-Core pass could invalidate this reasoning, but that's too bad.
|
|
| 670 | -It's still an invariant of Core programs generated by GHC from Haskell, and
|
|
| 671 | -Core-to-Core passes maintain it.
|
|
| 668 | +(1) Unary classes
|
|
| 669 | + |
|
| 670 | +The dictionary of a unary class is not a data constructor application at all:
|
|
| 671 | +it *is* its single field. See Note [Unary class magic] in GHC.Core.TyCon.
|
|
| 672 | +So for
|
|
| 673 | + |
|
| 674 | + class Eq a => C1 a where { op :: a -> a } -- Two fields: not unary
|
|
| 675 | + class Eq a => C2 a -- One field: unary
|
|
| 676 | + class D a where { meth :: a -> a } -- One field: unary
|
|
| 677 | + |
|
| 678 | +a (C1 a) dictionary is a data constructor application, so it cannot be bottom.
|
|
| 679 | +A (C2 a) dictionary is precisely the (Eq a) dictionary it wraps, which cannot
|
|
| 680 | +be bottom either. But a (D a) dictionary is precisely the function `meth`, and
|
|
| 681 | +the programmer can write
|
|
| 682 | + |
|
| 683 | + instance D Int where meth = undefined
|
|
| 684 | + |
|
| 685 | +so it can perfectly well bottom.
|
|
| 686 | + |
|
| 687 | +`isTerminatingType` therefore looks through unary classes, one field at a time,
|
|
| 688 | +until it reaches a type that is not a unary class:
|
|
| 689 | + |
|
| 690 | + isTerminatingType (C1 a) = True -- Stops at once: C1 is not unary
|
|
| 691 | + isTerminatingType (C2 a) = True -- Looks through C2, reaches (Eq a)
|
|
| 692 | + isTerminatingType (D a) = False -- Looks through D, reaches (a -> a)
|
|
| 693 | + |
|
| 694 | +(2) Two kinds of callers, pulling in opposite directions
|
|
| 695 | + |
|
| 696 | +Some code __relies__ on a dictionary being non-bottom, and evaluates one eagerly
|
|
| 697 | +on the basis of that:
|
|
| 698 | + |
|
| 699 | + * -fdicts-strict makes dictionary arguments strict.
|
|
| 700 | + See GHC.Types.Demand.strictifyDictDmd
|
|
| 701 | + |
|
| 702 | + * exprOkForSpeculation says that (eq_sel d) terminates, so that
|
|
| 703 | + case (eq_sel d) of _ -> blah
|
|
| 704 | + can be discarded (exprOkToDiscard), or evaluated ahead of time. See
|
|
| 705 | + Note [exprOkForSpeculation and type classes] in GHC.Core.Utils and
|
|
| 706 | + Note [Speculative evaluation] in GHC.CoreToStg.Prep
|
|
| 707 | + |
|
| 708 | +Other code would otherwise __create__ a bottom value at a dictionary type, and
|
|
| 709 | +it must not:
|
|
| 710 | + |
|
| 711 | + * Worker/wrapper replaces an absent argument with an error thunk or a
|
|
| 712 | + rubbish literal. See Note [Don't make fillers for terminating types]
|
|
| 713 | + in GHC.Core.Opt.WorkWrap.Utils
|
|
| 714 | + |
|
| 715 | +isTerminatingType is the condition used by both, so we the latter
|
|
| 716 | +kind of caller doesn't put bottoms in places where the former kind of
|
|
| 717 | +caller requires them to be absent.
|
|
| 718 | + |
|
| 719 | +The result of `isTerminatingType` depnds only on the type at the end of the
|
|
| 720 | +chain of fields, so `isTerminatingType` returns the same thing for a unary class
|
|
| 721 | +and its field: if it says False for (C2 a) then it also says False for (Eq a).
|
|
| 722 | +#27627 is an example of how we got it wrong before. `isTerminatingType` used
|
|
| 723 | +to stop at the unary class C2 rather than look through it, so worker/wrapper
|
|
| 724 | +made an error thunk for a (C2 a) dictionary, while exprOkForSpeculation
|
|
| 725 | +evaluated that very same value as a (Eq a) dictionary.
|
|
| 726 | + |
|
| 727 | +(3) Cyclic superclasses
|
|
| 728 | + |
|
| 729 | +With UndecidableSuperClasses the chain of fields can loop back on itself:
|
|
| 730 | + |
|
| 731 | + class D2 a => D1 a
|
|
| 732 | + class D1 a => D2 a
|
|
| 733 | + |
|
| 734 | +`isTerminatingType` returns false as soon as it detects a cycle
|
|
| 735 | + |
|
| 736 | +(4) Keeping the invariant true
|
|
| 737 | + |
|
| 738 | +We can check the invariant in core lint: `lintLetBind` rejects a binding
|
|
| 739 | +whose type is terminating and whose right hand side is bottom. It checks
|
|
| 740 | +bindings only, which is where the absent-filler machinery of worker/wrapper and
|
|
| 741 | +Specialise puts such values.
|
|
| 672 | 742 | |
| 673 | -Why is it useful to know that dictionaries are non-bottom?
|
|
| 743 | +However, there is a complication. With -fdefer-type-errors (and
|
|
| 744 | +-fdefer-typed-holes, and friends) GHC binds a dictionary it could not solve to
|
|
| 745 | +`typeError`, which is bottom:
|
|
| 674 | 746 | |
| 675 | -1. It justifies the use of `-XDictsStrict`;
|
|
| 676 | - see `GHC.Core.Types.Demand.strictifyDictDmd`
|
|
| 747 | + $dEq :: Eq T
|
|
| 748 | + $dEq = case typeError "No instance for (Eq T) ..."# of {}
|
|
| 677 | 749 | |
| 678 | -2. It means that (eq_sel d) is ok-for-speculation and thus
|
|
| 679 | - case (eq_sel d) of _ -> blah
|
|
| 680 | - can be discarded by the Simplifier. See these Notes:
|
|
| 681 | - Note [exprOkForSpeculation and type classes] in GHC.Core.Utils
|
|
| 682 | - Note[Speculative evaluation] in GHC.CoreToStg.Prep
|
|
| 750 | +`GHC.Core.Lint.isDeferredTypeError` handles this.
|
|
| 683 | 751 | |
| 684 | 752 | Note [Case expression invariants]
|
| 685 | 753 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| ... | ... | @@ -22,6 +22,7 @@ dataConUserTyVars :: DataCon -> [TyVar] |
| 22 | 22 | dataConUserTyVarBinders :: DataCon -> [TyVarBinder]
|
| 23 | 23 | dataConSourceArity :: DataCon -> Arity
|
| 24 | 24 | dataConFieldLabels :: DataCon -> [FieldLabel]
|
| 25 | +dataConInstArgTys :: DataCon -> [Type] -> [Scaled Type]
|
|
| 25 | 26 | dataConInstOrigArgTys :: DataCon -> [Type] -> [Scaled Type]
|
| 26 | 27 | dataConStupidTheta :: DataCon -> ThetaType
|
| 27 | 28 | dataConFullSig :: DataCon
|
| ... | ... | @@ -563,6 +563,15 @@ lintLetBind top_lvl rec_flag binder rhs rhs_ty |
| 563 | 563 | || exprIsTickedString rhs)
|
| 564 | 564 | (mkTopNonLitStrMsg binder)
|
| 565 | 565 | |
| 566 | + -- Check that we have not bound bottom at a type whose values are
|
|
| 567 | + -- assumed to be non-bottom, such as a dictionary. (See #24934, #25924, #27627).
|
|
| 568 | + -- A deferred type error is an exception.
|
|
| 569 | + -- See (4) in Note [NON-BOTTOM-DICTS invariant] in GHC.Core
|
|
| 570 | + ; checkL (not (isTerminatingType binder_ty)
|
|
| 571 | + || not (exprIsDeadEnd rhs)
|
|
| 572 | + || isDeferredTypeError rhs)
|
|
| 573 | + (mkBottomTerminatingTyMsg binder)
|
|
| 574 | + |
|
| 566 | 575 | ; flags <- getLintFlags
|
| 567 | 576 | |
| 568 | 577 | -- Check that a join-point binder has a valid type
|
| ... | ... | @@ -3814,6 +3823,28 @@ mkTopNonLitStrMsg :: Id -> SDoc |
| 3814 | 3823 | mkTopNonLitStrMsg binder
|
| 3815 | 3824 | = hsep [text "Top-level Addr# binder has a non-literal rhs:", ppr binder]
|
| 3816 | 3825 | |
| 3826 | +isDeferredTypeError :: CoreExpr -> Bool
|
|
| 3827 | +-- ^ True if it contains a call to `typeError` like -fdefer-type-errors and its
|
|
| 3828 | +-- relatives insert, such as
|
|
| 3829 | +-- case typeError @LiftedRep @() "No instance for ..."# of {}
|
|
| 3830 | +-- See (4) in Note [NON-BOTTOM-DICTS invariant] in GHC.Core
|
|
| 3831 | +isDeferredTypeError = go
|
|
| 3832 | + where
|
|
| 3833 | + go (Case scrut _ _ []) = go scrut
|
|
| 3834 | + go (App fun _) = go fun
|
|
| 3835 | + go (Cast expr _) = go expr
|
|
| 3836 | + go (Tick _ expr) = go expr
|
|
| 3837 | + go (Var v) = v `hasKey` typeErrorIdKey
|
|
| 3838 | + go _ = False
|
|
| 3839 | + |
|
| 3840 | +mkBottomTerminatingTyMsg :: Id -> SDoc
|
|
| 3841 | +-- See (4) in Note [NON-BOTTOM-DICTS invariant] in GHC.Core
|
|
| 3842 | +mkBottomTerminatingTyMsg binder
|
|
| 3843 | + = vcat [ text "Binder of a terminating type is bound to bottom:"
|
|
| 3844 | + , nest 2 (ppr binder <+> dcolon <+> ppr (idType binder))
|
|
| 3845 | + , text "A value of this type is assumed never to be bottom,"
|
|
| 3846 | + , text "so GHC may evaluate this binding without being asked to." ]
|
|
| 3847 | + |
|
| 3817 | 3848 | mkKindErrMsg :: TyVar -> Type -> SDoc
|
| 3818 | 3849 | mkKindErrMsg tyvar arg_ty
|
| 3819 | 3850 | = vcat [text "Kinds don't match in type application:",
|
| ... | ... | @@ -1326,8 +1326,8 @@ fragile |
| 1326 | 1326 | Note [Don't make fillers for terminating types]
|
| 1327 | 1327 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 1328 | 1328 | We never make an absent filler, error thunk or rubbish literal, for a terminating
|
| 1329 | -type (isTerminatingType): a non-unary class dictionary, a boxed equality, or a
|
|
| 1330 | -constraint tuple.
|
|
| 1329 | +type (isTerminatingType): a non-unary class dictionary, a unary class dictionary
|
|
| 1330 | +that wraps a non-unary class dictionary, a boxed equality, or a constraint tuple.
|
|
| 1331 | 1331 | |
| 1332 | 1332 | GHC relies on a dictionary value never being bottom (see
|
| 1333 | 1333 | Note [NON-BOTTOM-DICTS invariant] in GHC.Core). GHC uses "speculation" to
|
| ... | ... | @@ -1338,6 +1338,7 @@ rise to a succession of bugs including: |
| 1338 | 1338 | |
| 1339 | 1339 | * #24934: we evaluated an absent dictionary
|
| 1340 | 1340 | * #25924: we selected a superclass from an absent dictionary
|
| 1341 | + * #27627: we selected a superclass from an absent dictionary of a unary class
|
|
| 1341 | 1342 | |
| 1342 | 1343 | A terminating type is exactly what speculation will force: see
|
| 1343 | 1344 | Note [exprOkForSpeculation and type classes] in GHC.Core.Utils. So we refuse to
|
| ... | ... | @@ -1503,8 +1503,15 @@ There are a number of wrinkles |
| 1503 | 1503 | Rather, it has its own AlgTyConRhs, namely `UnaryClassTyCon`
|
| 1504 | 1504 | |
| 1505 | 1505 | (UCM3) Unlike non-unary classes, a value of type (C ty), where `C` is a unary
|
| 1506 | - class, might be bottom, because it is represented by the method type alone.
|
|
| 1507 | - See GHC.Core.Type.isTerminatingType.
|
|
| 1506 | + class, might be bottom, because it is represented by its single field alone.
|
|
| 1507 | + It is bottom exactly when a value of that field's type can be bottom, so
|
|
| 1508 | + GHC.Core.Type.isTerminatingType looks through unary classes to decide. For
|
|
| 1509 | + |
|
| 1510 | + class Eq a => C2 a -- Field: an (Eq a) dictionary
|
|
| 1511 | + class D a where { meth :: a -> a } -- Field: a function
|
|
| 1512 | + |
|
| 1513 | + a (C2 a) dictionary cannot be bottom but a (D a) dictionary can.
|
|
| 1514 | + See Note [NON-BOTTOM-DICTS invariant] in GHC.Core.
|
|
| 1508 | 1515 | |
| 1509 | 1516 | Similarly in exprOkForSpeculation/exprOkToDiscard/exprOkForSpecEval,
|
| 1510 | 1517 | in GHC.Core.Utils. In the utility funcion `app_ok` we need a special
|
| ... | ... | @@ -133,6 +133,7 @@ module GHC.Core.Type ( |
| 133 | 133 | definitelyLiftedType, definitelyUnliftedType,
|
| 134 | 134 | isAlgType, isDataFamilyApp, isSatTyFamApp,
|
| 135 | 135 | isPrimitiveType, isStrictType, isTerminatingType,
|
| 136 | + unwrapUnaryClasses,
|
|
| 136 | 137 | isLevityTy, isLevityVar,
|
| 137 | 138 | isRuntimeRepTy, isRuntimeRepVar, isRuntimeRepKindedTy,
|
| 138 | 139 | dropRuntimeRepArgs,
|
| ... | ... | @@ -235,8 +236,11 @@ import GHC.Types.Var.Env |
| 235 | 236 | import GHC.Types.Var.Set
|
| 236 | 237 | |
| 237 | 238 | import GHC.Core.TyCon
|
| 239 | +import GHC.Core.TyCon.Set( TyConSet, emptyTyConSet, elemTyConSet, extendTyConSet )
|
|
| 238 | 240 | import GHC.Builtin.Types.Prim
|
| 239 | 241 | |
| 242 | +import {-# SOURCE #-} GHC.Core.DataCon( dataConInstArgTys )
|
|
| 243 | + |
|
| 240 | 244 | import {-# SOURCE #-} GHC.Builtin.Types
|
| 241 | 245 | ( charTy, naturalTy
|
| 242 | 246 | , typeSymbolKind, liftedTypeKind, unliftedTypeKind
|
| ... | ... | @@ -2460,12 +2464,50 @@ isTerminatingType :: HasDebugCallStack => Type -> Bool |
| 2460 | 2464 | -- Note [NON-BOTTOM-DICTS invariant] in GHC.Core
|
| 2461 | 2465 | -- NB: unlifted types are not terminating types!
|
| 2462 | 2466 | -- e.g. you can write a term (loop 1)::Int# that diverges.
|
| 2463 | -isTerminatingType ty = case tyConAppTyCon_maybe ty of
|
|
| 2467 | +isTerminatingType ty = case tyConAppTyCon_maybe (unwrapUnaryClasses id ty) of
|
|
| 2464 | 2468 | Just tc -> isClassTyCon tc && not (isUnaryClassTyCon tc)
|
| 2465 | - -- A non-unary class TyCon is terminating
|
|
| 2466 | - -- See (UCM3) in Note [Unary class magic] in GHC.Core.TyCon
|
|
| 2469 | + -- We ask about the type that represents the dictionary, not the
|
|
| 2470 | + -- type we were handed, because a unary class dictionary **is** the
|
|
| 2471 | + -- field it wraps. A non-unary class TyCon is terminating; a
|
|
| 2472 | + -- unary one is left here only when there is a cylce,
|
|
| 2473 | + -- and then the dictionary really can be bottom.
|
|
| 2474 | + -- See (1) and (3) in Note [NON-BOTTOM-DICTS invariant] in
|
|
| 2475 | + -- GHC.Core, and (UCM3) in Note [Unary class magic]
|
|
| 2467 | 2476 | _ -> False
|
| 2468 | 2477 | |
| 2478 | +-- | Look through unary classes, and return the first type
|
|
| 2479 | +-- that is not a unary class. For
|
|
| 2480 | +--
|
|
| 2481 | +-- class Eq a => C2 a -- Unary
|
|
| 2482 | +-- class D a where { meth :: a -> a } -- Unary
|
|
| 2483 | +-- class Eq a => C1 a where { op :: a -> a } -- Not unary
|
|
| 2484 | +--
|
|
| 2485 | +-- this returns (Eq a) for (C2 a), (a -> a) for (D a), and (C1 a) unchanged.
|
|
| 2486 | +--
|
|
| 2487 | +-- With UndecidableSuperClasses we can have a cycle:
|
|
| 2488 | +--
|
|
| 2489 | +-- class D2 a => D1 a
|
|
| 2490 | +-- class D1 a => D2 a
|
|
| 2491 | +--
|
|
| 2492 | +-- so we stop and return when we detect a cycle.
|
|
| 2493 | +-- See Note [Unary class magic] in GHC.Core.TyCon
|
|
| 2494 | +unwrapUnaryClasses :: (Type -> Type) -- ^ Normalise a type before each step.
|
|
| 2495 | + -- Pass 'id' to leave it alone, or
|
|
| 2496 | + -- 'GHC.Types.RepType.unwrapType' to see
|
|
| 2497 | + -- through newtypes, casts and foralls
|
|
| 2498 | + -> Type -> Type
|
|
| 2499 | +unwrapUnaryClasses norm = go emptyTyConSet
|
|
| 2500 | + where
|
|
| 2501 | + go :: TyConSet -> Type -> Type
|
|
| 2502 | + go seen ty
|
|
| 2503 | + | Just (tc, tys) <- splitTyConApp_maybe (norm ty)
|
|
| 2504 | + , Just (_cls, dict_con) <- isUnaryClassTyCon_maybe tc
|
|
| 2505 | + , [fld_ty] <- map scaledThing (dataConInstArgTys dict_con tys)
|
|
| 2506 | + , not (tc `elemTyConSet` seen)
|
|
| 2507 | + = go (seen `extendTyConSet` tc) fld_ty
|
|
| 2508 | + | otherwise
|
|
| 2509 | + = ty
|
|
| 2510 | + |
|
| 2469 | 2511 | isPrimitiveType :: Type -> Bool
|
| 2470 | 2512 | -- ^ Returns true of types that are opaque to Haskell.
|
| 2471 | 2513 | isPrimitiveType ty = case splitTyConApp_maybe ty of
|
| ... | ... | @@ -2383,8 +2383,10 @@ a) That the function is a class-op, with IdDetails of ClassOpId |
| 2383 | 2383 | b) That the result type of the class-op is terminating or unlifted. E.g. for
|
| 2384 | 2384 | class C a => D a where ...
|
| 2385 | 2385 | class C a where { op :: a -> a }
|
| 2386 | - Since C is represented by a newtype, (sc_sel (d :: D a)) might
|
|
| 2387 | - not be terminating.
|
|
| 2386 | + C is a unary class whose one field is the method `op`, so a (C a) dictionary
|
|
| 2387 | + **is** that function and can be bottom. Hence
|
|
| 2388 | + (sc_sel (d :: D a)) might not be terminating.
|
|
| 2389 | + See (1) in Note [NON-BOTTOM-DICTS invariant] in GHC.Core.
|
|
| 2388 | 2390 | |
| 2389 | 2391 | Rather than repeatedly test if the result of the class-op is a
|
| 2390 | 2392 | terminating/unlifted type, we cache it as a field of ClassOpId. See
|
| ... | ... | @@ -32,7 +32,6 @@ import GHC.Types.Basic (Arity, RepArity) |
| 32 | 32 | import GHC.Core.DataCon
|
| 33 | 33 | import GHC.Core.Coercion
|
| 34 | 34 | import GHC.Core.TyCon
|
| 35 | -import GHC.Core.TyCon.Set
|
|
| 36 | 35 | import GHC.Core.TyCon.RecWalk
|
| 37 | 36 | import GHC.Core.TyCo.Rep
|
| 38 | 37 | import GHC.Core.Type
|
| ... | ... | @@ -734,26 +733,13 @@ mightBeFunTy ty |
| 734 | 733 | -- definitely not a function type.
|
| 735 | 734 | | definitelyUnliftedType ty
|
| 736 | 735 | = False
|
| 737 | - | Just tc <- tyConAppTyCon_maybe (unwrap_type ty)
|
|
| 736 | + -- Use 'unwrapType' to look through casts, newtypes and foralls, and
|
|
| 737 | + -- 'unwrapUnaryClasses' to look through unary classes (which are transparent
|
|
| 738 | + -- as per Note [Unary class magic] in GHC.Core.TyCon). We don't try to
|
|
| 739 | + -- reduce type family applications, as we don't have a FamInstEnv to hand.
|
|
| 740 | + | Just tc <- tyConAppTyCon_maybe (unwrapUnaryClasses unwrapType ty)
|
|
| 738 | 741 | -- A proper datatype (such as 'Int' or 'Maybe Bool') is definitely not
|
| 739 | 742 | -- a function type. (This does not include newtypes nor type families.)
|
| 740 | 743 | = not $ isBoxedDataTyCon tc
|
| 741 | 744 | | otherwise
|
| 742 | 745 | = True |
| 743 | - |
|
| 744 | - where
|
|
| 745 | - -- Use 'unwrapType' to look through casts, newtypes and foralls.
|
|
| 746 | - -- Separately, look through unary classes (supposed to be transparent as per
|
|
| 747 | - -- Note [Unary class magic] in GHC.Core.TyCon). We don't try to reduce type
|
|
| 748 | - -- family applications, as we don't have a FamInstEnv to hand.
|
|
| 749 | - unwrap_type = go emptyTyConSet
|
|
| 750 | - where
|
|
| 751 | - go seen_tcs ty
|
|
| 752 | - | Just (tc, tys) <- splitTyConApp_maybe (unwrapType ty)
|
|
| 753 | - , Just (_cls, unary_dc) <- isUnaryClassTyCon_maybe tc
|
|
| 754 | - , [inst_meth_ty] <- map scaledThing (dataConInstArgTys unary_dc tys)
|
|
| 755 | - = if tc `elemTyConSet` seen_tcs
|
|
| 756 | - then ty -- cycle detected: bail out
|
|
| 757 | - else go (seen_tcs `extendTyConSet` tc) inst_meth_ty
|
|
| 758 | - | otherwise
|
|
| 759 | - = ty |
| 1 | +{-# LANGUAGE GADTs, ConstraintKinds, ScopedTypeVariables #-}
|
|
| 2 | +{-# OPTIONS_GHC -fno-worker-wrapper #-}
|
|
| 3 | +module Callee where
|
|
| 4 | + |
|
| 5 | +-- TC is not unary: it has a superclass field and a method field. So $p1TC is
|
|
| 6 | +-- an ordinary selector, ($p1TC d) is not a trivial expression, and CorePrep
|
|
| 7 | +-- gives it a binding of its own.
|
|
| 8 | +class Eq a => TC a where
|
|
| 9 | + tcDummy :: a -> Int
|
|
| 10 | + |
|
| 11 | +-- UC is unary: a single superclass field. Its dictionary is precisely the
|
|
| 12 | +-- (TC a) dictionary it wraps, so it cannot be bottom either.
|
|
| 13 | +class TC a => UC a where {}
|
|
| 14 | + |
|
| 15 | +instance TC Int where tcDummy _ = 0
|
|
| 16 | +instance UC Int
|
|
| 17 | + |
|
| 18 | +data Dict c where
|
|
| 19 | + Dict :: c => Dict c
|
|
| 20 | + |
|
| 21 | +-- Ignores its argument, so the Dict below is absent-demanded, and hence so is
|
|
| 22 | +-- the UC dictionary that Dict carries.
|
|
| 23 | +{-# NOINLINE discard #-}
|
|
| 24 | +discard :: Dict c -> Int
|
|
| 25 | +discard _ = 42
|
|
| 26 | + |
|
| 27 | +-- The Core of the body is
|
|
| 28 | +-- discard (Dict @(Eq a) ($p1TC ($p1UC d)))
|
|
| 29 | +-- The constructor application is a value, so CorePrep floats the selection
|
|
| 30 | +-- out of the argument and evaluates it at the head of b. -fno-worker-wrapper
|
|
| 31 | +-- keeps the dictionary parameter, so the caller has to pass one.
|
|
| 32 | +{-# NOINLINE b #-}
|
|
| 33 | +b :: forall a. UC a => a -> Int
|
|
| 34 | +b _ = discard (Dict :: Dict (Eq a)) |
| 1 | +module Caller where
|
|
| 2 | + |
|
| 3 | +import Callee
|
|
| 4 | + |
|
| 5 | +-- b does not use its dictionary, so a's dictionary is absent. Worker/wrapper
|
|
| 6 | +-- used to drop it and pass an error thunk to b in its place, which b then
|
|
| 7 | +-- evaluated. Now isTerminatingType looks through the unary class UC, sees the
|
|
| 8 | +-- (TC a) dictionary underneath, and refuses to make a filler.
|
|
| 9 | +{-# NOINLINE a #-}
|
|
| 10 | +a :: UC t => t -> Int
|
|
| 11 | +a x = b x + 1 |
| 1 | +module Main where
|
|
| 2 | +import Caller
|
|
| 3 | +main :: IO ()
|
|
| 4 | +main = print (a (1 :: Int)) |
| 1 | +43 |
| 1 | +test('T27627',
|
|
| 2 | + [extra_files(['Main.hs', 'Caller.hs', 'Callee.hs'])],
|
|
| 3 | + multimod_compile_and_run,
|
|
| 4 | + ['Main', '-O']) |