Zubin pushed to branch wip/27627 at Glasgow Haskell Compiler / GHC

Commits:

14 changed files:

Changes:

  • changelog.d/27627
    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

  • compiler/GHC/Core.hs
    ... ... @@ -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
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

  • compiler/GHC/Core/DataCon.hs-boot
    ... ... @@ -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
    

  • compiler/GHC/Core/Lint.hs
    ... ... @@ -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:",
    

  • compiler/GHC/Core/Opt/WorkWrap/Utils.hs
    ... ... @@ -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
    

  • compiler/GHC/Core/TyCon.hs
    ... ... @@ -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
    

  • compiler/GHC/Core/Type.hs
    ... ... @@ -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
    

  • compiler/GHC/Core/Utils.hs
    ... ... @@ -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
    

  • compiler/GHC/Types/RepType.hs
    ... ... @@ -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

  • testsuite/tests/core-to-stg/T27627/Callee.hs
    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))

  • testsuite/tests/core-to-stg/T27627/Caller.hs
    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

  • testsuite/tests/core-to-stg/T27627/Main.hs
    1
    +module Main where
    
    2
    +import Caller
    
    3
    +main :: IO ()
    
    4
    +main = print (a (1 :: Int))

  • testsuite/tests/core-to-stg/T27627/T27627.stdout
    1
    +43

  • testsuite/tests/core-to-stg/T27627/all.T
    1
    +test('T27627',
    
    2
    +     [extra_files(['Main.hs', 'Caller.hs', 'Callee.hs'])],
    
    3
    +     multimod_compile_and_run,
    
    4
    +     ['Main', '-O'])