Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC

Commits:

4 changed files:

Changes:

  • changelog.d/T27374
    1
    +section: compiler
    
    2
    +issues: #27374
    
    3
    +mrs: !16207
    
    4
    +synopsis:
    
    5
    +  Fix optimisation of InstCo coercions
    
    6
    +description:
    
    7
    +  Fixes a bug in which the coercion optimiser would incorrectly optimise certain
    
    8
    +  InstCo coercions, incorrectly changing their kinds.
    
    9
    +

  • compiler/GHC/Core/Coercion/Opt.hs
    ... ... @@ -274,10 +274,11 @@ opt_co4, opt_co4' :: LiftingContext -> SwapFlag -> ReprFlag
    274 274
     -- Precondition:  In every call (opt_co4 lc sym rep role co)
    
    275 275
     --                we should have role = coercionRole co
    
    276 276
     -- Precondition:  role is not Phantom
    
    277
    --- Postcondition: The resulting coercion is equivalant to
    
    278
    ---                     wrapsub (wrapsym (mksub co)
    
    279
    ---                 where wrapsym is SymCo if sym=True
    
    280
    ---                       wrapsub is SubCo if rep=True
    
    277
    +-- Postcondition: The resulting coercion is equivalent to
    
    278
    +--                     wrapSub (wrapSym (substCo co))
    
    279
    +--                 where substCo applies the LiftingContext substitution
    
    280
    +--                       wrapSym wraps in SymCo when the ambient Sym is IsSwapped
    
    281
    +--                       wrapSub wraps in SubCo when rep=True
    
    281 282
     
    
    282 283
     -- opt_co4 is there just to support tracing, when debugging
    
    283 284
     -- Usually it just goes straight to opt_co4'
    
    ... ... @@ -327,7 +328,10 @@ opt_co4' env sym rep r (GRefl _r ty (MCo kco))
    327 328
                    text "Type:" <+> ppr ty) $
    
    328 329
         if isReflKindCo kco || isReflKindCo kco'
    
    329 330
         then wrapSym sym ty_co
    
    330
    -    else wrapSym sym $ mk_coherence_right_co r' (coercionRKind ty_co) kco' ty_co
    
    331
    +    else
    
    332
    +      -- Keep 'sym' on the outside instead of trying to push it in, to avoid
    
    333
    +      -- duplicating 'k_co' in 'GRefl r (ty |> kco) (MCo (sym kco))'
    
    334
    +      wrapSym sym $ mk_coherence_right_co r' (coercionRKind ty_co) kco' ty_co
    
    331 335
                 -- ty :: k1
    
    332 336
                 -- kco :: k1 ~ k2
    
    333 337
                 -- Desired result coercion:   ty ~ ty |> co
    
    ... ... @@ -534,25 +538,36 @@ opt_co4' env sym rep r (InstCo fun_co arg_co)
    534 538
       , let s2'   = coercionRKind arg_co'
    
    535 539
             tv_co = mk_coherence_right_co Nominal s2' (mkSymCo k_co') arg_co'
    
    536 540
             env'  = extendLiftingContext (zapLiftingContext env) tv' tv_co
    
    537
    -  = opt_co4 env' NotSwapped False r' body_co'
    
    541
    +  = opt_co4 env' sym False r' body_co'
    
    538 542
     
    
    539 543
         -- See Note [Forall over coercion]
    
    540 544
       | Just (cv', _visL, _visR, _kind_co', body_co') <- splitForAllCo_co_maybe fun_co'
    
    541 545
       , CoercionTy h1' <- coercionLKind arg_co'
    
    542 546
       , let env' = extendLiftingContextCvSubst (zapLiftingContext env) cv' h1'
    
    543
    -  = opt_co4 env' NotSwapped False r' body_co'
    
    547
    +  = opt_co4 env' sym False r' body_co'
    
    544 548
     
    
    545 549
       -- Those cases didn't work either, so rebuild the InstCo
    
    546
    -  -- Push Sym into /both/ function /and/ arg_coument
    
    547
    -  | otherwise = InstCo fun_co' arg_co'
    
    550
    +  | otherwise = InstCo sym_fun_co' sym_arg_co'
    
    548 551
     
    
    549 552
       where
    
    550
    -    -- fun_co' arg_co' are both optimised, /and/ we have pushed `sym` into both
    
    551
    -    -- So no more sym'ing on th results of fun_co' arg_co'
    
    552
    -    fun_co' = opt_co4 env sym rep r fun_co
    
    553
    -    arg_co' = opt_co4 env sym False Nominal arg_co
    
    554 553
         r'   = chooseRole rep r
    
    555 554
     
    
    555
    +    -- Optimised versions of fun_co & arg_co.
    
    556
    +    -- NB: we do /not/ push in `sym` (hence using `NotSwappped`),
    
    557
    +    -- in order to respect (LC2) in Note [The LiftingContext in optCoercion].
    
    558
    +    fun_co'     = opt_co4 env NotSwapped rep   r       fun_co
    
    559
    +    arg_co'     = opt_co4 env NotSwapped False Nominal arg_co
    
    560
    +
    
    561
    +    -- Like fun_co'/arg_co', except we /have/ pushed in `sym`.
    
    562
    +    -- We use 'mkDeepSymCo' to push in 'sym' without re-optimising.
    
    563
    +    -- See Note [Pushing Sym without re-optimising]
    
    564
    +    sym_fun_co'
    
    565
    +      | isSwapped sym = mkDeepSymCo fun_co'
    
    566
    +      | otherwise     = fun_co'
    
    567
    +    sym_arg_co'
    
    568
    +      | isSwapped sym = mkDeepSymCo arg_co'
    
    569
    +      | otherwise     = arg_co'
    
    570
    +
    
    556 571
     opt_co4' env sym _rep r (KindCo co)
    
    557 572
       = assert (r == Nominal) $
    
    558 573
         let kco' = promoteCoercion co in
    
    ... ... @@ -575,6 +590,83 @@ We do so here in optCoercion, not in mkCoVarCo; see Note [mkCoVarCo]
    575 590
     in GHC.Core.Coercion.
    
    576 591
     -}
    
    577 592
     
    
    593
    +{- Note [Pushing Sym without re-optimising]
    
    594
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    595
    +To optimise (InstCo fun_co arg_co) we must first optimise fun_co and arg_co
    
    596
    +/without/ the ambient Sym, as required by (LC2) of
    
    597
    +Note [The LiftingContext in optCoercion].
    
    598
    +
    
    599
    +But in the fallback case (when the optimised fun_co is not a ForAllCo), we need
    
    600
    +to push the ambient Sym into both components when rebuilding the InstCo.
    
    601
    +
    
    602
    +  Re-running the optimiser with sym=IsSwapped would optimise each component twice.
    
    603
    +  As the components can themselves contain 'InstCo's, this has the potential to
    
    604
    +  trigger exponential behaviour.
    
    605
    +
    
    606
    +  Simply wrapping 'sym' on the outside would fail to push 'sym' deeply into the
    
    607
    +  coercion, which would be in tension with the rest of the coercion optimiser
    
    608
    +  which relies on 'sym' being pushed into the leaves to expose cancellation
    
    609
    +  opportunities.
    
    610
    +
    
    611
    +To solve this, we define 'mkDeepSymCo': it pushes a Sym into an already
    
    612
    +optimised coercion. This is much simpler than full coercion optimisation, as it
    
    613
    +doesn't need to do coercion lifting nor downgrade roles.
    
    614
    +-}
    
    615
    +
    
    616
    +-- | Push 'Sym' deeply into an already-optimised coercion.
    
    617
    +--
    
    618
    +-- Morally the same as re-optimising the coercion with @sym=IsSwapped@, but
    
    619
    +-- more efficient.
    
    620
    +--
    
    621
    +-- See Note [Pushing Sym without re-optimising]
    
    622
    +mkDeepSymCo :: NormalCo -> NormalCo
    
    623
    +mkDeepSymCo = go
    
    624
    +  where
    
    625
    +    go :: NormalCo -> NormalCo
    
    626
    +    -- Straightforward cases
    
    627
    +    go (SymCo co)                                 = co
    
    628
    +    go co@(UnivCo { uco_lty = t1, uco_rty = t2 }) = co { uco_lty = t2, uco_rty = t1 }
    
    629
    +    go (TyConAppCo r tc cos)                      = TyConAppCo r tc $ map go cos
    
    630
    +    go (AppCo co1 co2)                            = AppCo (go co1) (go co2)
    
    631
    +    go (FunCo r afl afr cow co1 co2)              = FunCo r afr afl (go cow) (go co1) (go co2)
    
    632
    +    go (TransCo co1 co2)                          = TransCo (go co2) (go co1)
    
    633
    +    go (SelCo cs co)                              = SelCo cs $ go co
    
    634
    +    go (LRCo lr co)                               = LRCo lr $ go co
    
    635
    +    go (InstCo fun_co arg_co)                     = InstCo (go fun_co) (go arg_co)
    
    636
    +    go (KindCo co)                                = KindCo $ go co
    
    637
    +    go (SubCo co)                                 = SubCo $ go co
    
    638
    +    go co@(CoVarCo {})                            = SymCo co
    
    639
    +    go co@(Refl {})                               = co
    
    640
    +    go co@(GRefl _ _ MRefl)                       = co
    
    641
    +    go co@(GRefl _ _ (MCo {}))                    = SymCo co
    
    642
    +      -- keep the sym outside, like the GRefl case of opt_co4' does, instead of
    
    643
    +      --   GRefl r (ty |> kco) (MCo (sym kco))
    
    644
    +      -- as that duplicates 'kco'
    
    645
    +    go co@(AxiomCo {})                           =  SymCo co
    
    646
    +      -- Same as in opt_co4': do *not* push sym inside top-level axioms.
    
    647
    +
    
    648
    +    go co@(ForAllCo { fco_tcv = tcv, fco_visL = visL, fco_visR = visR
    
    649
    +                    , fco_kind = k_mco, fco_body = body_co })
    
    650
    +      = case k_mco of
    
    651
    +          MRefl -> ForAllCo tcv visR visL k_mco (go body_co)
    
    652
    +          MCo {} ->
    
    653
    +            -- Pushing 'sym' into the kind coercion would require threading a
    
    654
    +            -- substitution through, as per Note [Optimising ForAllCo].
    
    655
    +            -- This wouldn't be difficult (see commented out code below), but
    
    656
    +            -- for now we prefer to keep 'mkDeepSymCo' as simple as possible.
    
    657
    +            SymCo co
    
    658
    +
    
    659
    +            -- Pushing 'sym' into the kind coercion, threading 'Subst' through:
    
    660
    +            --
    
    661
    +            -- = ForAllCo tcv' visR visL k_mco' (go subst' body_co)
    
    662
    +            -- where
    
    663
    +            --   k_mco' = case k_mco of
    
    664
    +            --              MRefl  -> MRefl
    
    665
    +            --              MCo co -> MCo (go subst co)
    
    666
    +            --   (subst', tcv') = forAllCoBndrSubst IsSwapped tcv k_mco' subst
    
    667
    +
    
    668
    +    go (HoleCo h) = pprPanic "mkDeepSymCo: HoleCo" (ppr h)
    
    669
    +
    
    578 670
     -------------
    
    579 671
     -- | Optimize a phantom coercion. The input coercion may not necessarily
    
    580 672
     -- be a phantom, but the output sure will be.
    
    ... ... @@ -1450,6 +1542,9 @@ But if sym=Swapped, things are trickier. Here is an identity that helps:
    1450 1542
           of tv:k1 in bodyco by (tv:k2 |> Sym kco)
    
    1451 1543
       This mirrors what happens in the typing rule for ForAllCo
    
    1452 1544
       See Note [ForAllCo] in GHC.Core.TyCo.Rep
    
    1545
    +  NB: doing so inlines 'kco' at all occurrences of tv, duplicating it. This is
    
    1546
    +      inconsistent with how we optimise GRefl, where we keep the 'Sym' on the
    
    1547
    +      outside to avoid duplicating the kind coercion.
    
    1453 1548
     
    
    1454 1549
     -}
    
    1455 1550
     optForAllCoBndr :: LiftingContext -> SwapFlag
    
    ... ... @@ -1464,13 +1559,20 @@ optForAllCoBndr env sym tcv k_mco
    1464 1559
                     MRefl -> MRefl
    
    1465 1560
                     MCo co -> MCo (opt_co4 env sym False Nominal co)
    
    1466 1561
     
    
    1467
    -    (env', tcv') = updateLCSubst env upd_subst
    
    1468
    -
    
    1469
    -    upd_subst :: Subst -> (Subst, TyCoVar)
    
    1470
    -    upd_subst subst
    
    1471
    -      | isTyVar tcv = upd_subst_tv subst
    
    1472
    -      | otherwise   = upd_subst_cv subst
    
    1473
    -
    
    1562
    +    (env', tcv') = updateLCSubst env (forAllCoBndrSubst sym tcv k_mco')
    
    1563
    +
    
    1564
    +-- | Substitute a 'ForAllCo' binder, returning the body substitution.
    
    1565
    +--
    
    1566
    +-- See Note [Optimising ForAllCo].
    
    1567
    +forAllCoBndrSubst
    
    1568
    +  :: SwapFlag
    
    1569
    +  -> TyCoVar     -- ^ the ForAllCo binder
    
    1570
    +  -> MCoercionN  -- ^ its kind coercion, with the ambient Sym already pushed into it
    
    1571
    +  -> Subst -> (Subst, TyCoVar)
    
    1572
    +forAllCoBndrSubst sym tcv k_mco'
    
    1573
    +  | isTyVar tcv = upd_subst_tv
    
    1574
    +  | otherwise   = upd_subst_cv
    
    1575
    +  where
    
    1474 1576
         upd_subst_tv subst
    
    1475 1577
           = case k_mco' of
    
    1476 1578
               MCo k_co' | isSwapped sym -> (subst2, tv2)
    

  • testsuite/tests/corelint/T27374.hs
    1
    +{-# LANGUAGE BlockArguments #-}
    
    2
    +
    
    3
    +-- | Regression test for #27374:
    
    4
    +--
    
    5
    +--  - manually construct some coercions that are not InstCo but become InstCo
    
    6
    +--    after optimisation
    
    7
    +--  - check that the coercion optimiser properly optimises these by running
    
    8
    +--    Core Lint on the output
    
    9
    +
    
    10
    +module Main (main) where
    
    11
    +
    
    12
    +-- base 
    
    13
    +import Control.Monad.IO.Class
    
    14
    +  ( liftIO )
    
    15
    +import Data.Foldable
    
    16
    +  ( for_ )
    
    17
    +import System.Environment
    
    18
    +  ( getArgs )
    
    19
    +
    
    20
    +-- ghc
    
    21
    +import GHC
    
    22
    +  ( runGhc, getSessionDynFlags, getLogger )
    
    23
    +import GHC.Builtin.Types
    
    24
    +  ( maybeTyCon, tupleTyCon, mkBoxedTupleTy )
    
    25
    +import GHC.Core
    
    26
    +  ( Expr(Coercion) )
    
    27
    +import GHC.Core.Coercion
    
    28
    +  ( Coercion, CoVar, MCoercion(..)
    
    29
    +  , Role(..), CoSel(..), FunSel(..)
    
    30
    +  , mkCoercionType
    
    31
    +  , mkCoVar, mkCoVarCo, mkForAllCo, mkInstCo, mkReflCo, mkNomReflCo
    
    32
    +  , mkSelCo, mkSymCo, mkTransCo, mkTyConAppCo
    
    33
    +  , tyCoVarsOfCo
    
    34
    +  )
    
    35
    +import GHC.Core.Coercion.Opt
    
    36
    +  ( OptCoercionOpts(..), optCoercion )
    
    37
    +import GHC.Core.Lint        
    
    38
    +  ( lintExpr )
    
    39
    +import GHC.Core.Predicate   
    
    40
    +  ( mkNomEqPred )
    
    41
    +import GHC.Core.TyCo.FVs    
    
    42
    +  ( tyCoVarsOfCoList )
    
    43
    +import GHC.Core.TyCo.Rep    
    
    44
    +  ( Type(CoercionTy) )
    
    45
    +import GHC.Core.Type
    
    46
    +
    
    47
    +import GHC.Data.FastString
    
    48
    +  ( fsLit )
    
    49
    +
    
    50
    +import GHC.Driver.Config.Core.Lint
    
    51
    +  ( initLintConfig )
    
    52
    +
    
    53
    +import GHC.Types.Basic
    
    54
    +  ( Boxity(Boxed) )
    
    55
    +import GHC.Types.Var
    
    56
    +  ( mkTyVar, coreTyLamForAllTyFlag )
    
    57
    +import GHC.Types.Var.Env        
    
    58
    +  ( mkInScopeSet )
    
    59
    +import GHC.Types.Name           
    
    60
    +  ( mkInternalName )
    
    61
    +import GHC.Types.Name.Occurrence
    
    62
    +  ( mkTyVarOccFS, mkVarOccFS )
    
    63
    +import GHC.Types.SrcLoc         
    
    64
    +  ( noSrcSpan )
    
    65
    +import GHC.Types.Unique         
    
    66
    +  ( mkUniqueGrimily )
    
    67
    +
    
    68
    +import GHC.Utils.Error     
    
    69
    +  ( pprMessageBag, putMsg )
    
    70
    +import GHC.Utils.Outputable
    
    71
    +  ( (<+>), text, vcat )
    
    72
    +
    
    73
    +--------------------------------------------------------------------------------
    
    74
    +-- Helpers for building fresh variables
    
    75
    +--------------------------------------------------------------------------------
    
    76
    +
    
    77
    +mkTv :: Int -> String -> Kind -> TyVar
    
    78
    +mkTv i s =
    
    79
    +  mkTyVar $
    
    80
    +    mkInternalName
    
    81
    +      ( mkUniqueGrimily (fromIntegral i) )
    
    82
    +      ( mkTyVarOccFS (fsLit s) )
    
    83
    +      noSrcSpan
    
    84
    +
    
    85
    +mkCv :: Int -> String -> Type -> CoVar
    
    86
    +mkCv i s =
    
    87
    +  mkCoVar $
    
    88
    +    mkInternalName
    
    89
    +      ( mkUniqueGrimily (fromIntegral i) )
    
    90
    +      ( mkVarOccFS (fsLit s) )
    
    91
    +      noSrcSpan
    
    92
    +
    
    93
    +--------------------------------------------------------------------------------
    
    94
    +-- ForAllCo-over-type
    
    95
    +--------------------------------------------------------------------------------
    
    96
    +
    
    97
    +co1 :: Coercion
    
    98
    +co1 =
    
    99
    +  -- Use mkTransCo to get a coercion that is not InstCo but optimises to InstCo
    
    100
    +  lhs `mkTransCo` rhs 
    
    101
    +  where
    
    102
    +    p = mkTv 21 "p" liftedTypeKind
    
    103
    +    q = mkTv 22 "q" liftedTypeKind
    
    104
    +    a = mkTv 23 "a" liftedTypeKind
    
    105
    +    b = mkTv 24 "b" liftedTypeKind
    
    106
    +
    
    107
    +    tup2 = tupleTyCon Boxed 2
    
    108
    +
    
    109
    +    -- cv :: p ~# q
    
    110
    +    cv = mkCoVarCo $
    
    111
    +           mkCv 25 "cv" $
    
    112
    +             mkCoercionType Nominal (mkTyVarTy p) (mkTyVarTy q)
    
    113
    +
    
    114
    +    -- arg :: Maybe q ~ Maybe p
    
    115
    +    arg = mkSymCo (mkTyConAppCo Nominal maybeTyCon [cv])
    
    116
    +
    
    117
    +    -- forallCo = <forall a b. (a, b) -> a>_N
    
    118
    +    forallCo =
    
    119
    +      mkReflCo Nominal $
    
    120
    +        mkSpecForAllTys [a, b] $
    
    121
    +          mkVisFunTyMany
    
    122
    +            (mkBoxedTupleTy [mkTyVarTy a, mkTyVarTy b])
    
    123
    +            (mkTyVarTy a)
    
    124
    +
    
    125
    +    -- forallCo @arg @arg
    
    126
    +    inst_co = mkInstCo (mkInstCo forallCo arg) arg
    
    127
    +
    
    128
    +    rhs   = mkSymCo (mkSelCo (SelFun SelArg) inst_co)
    
    129
    +    lhs   = mkTyConAppCo Nominal tup2 [arg, arg]
    
    130
    +
    
    131
    +--------------------------------------------------------------------------------
    
    132
    +-- ForAllCo-over-coercion
    
    133
    +--------------------------------------------------------------------------------
    
    134
    +
    
    135
    +co2 :: Coercion
    
    136
    +co2 =
    
    137
    +  -- This outer 'sym' was necessary to trigger the specific bug
    
    138
    +  mkSymCo $
    
    139
    +    mkInstCo ( mkSymCo g ) arg
    
    140
    +  where
    
    141
    +    k1  = mkTyVarTy $ mkTv 11 "k1" liftedTypeKind
    
    142
    +    k2  = mkTyVarTy $ mkTv 12 "k2" liftedTypeKind
    
    143
    +
    
    144
    +    x   = mkTv 13 "x" k1
    
    145
    +
    
    146
    +    -- cv :: k1 ~# Type
    
    147
    +    cv_ty = mkNomEqPred k1 liftedTypeKind
    
    148
    +    cv    = mkCv 14 "cv" cv_ty
    
    149
    +
    
    150
    +    -- hk :: (k1 ~# Type) ~# (k2 ~# Type)
    
    151
    +    hk  = mkCoVarCo (mkCv 15 "hk" (mkNomEqPred cv_ty (mkNomEqPred k2 liftedTypeKind)))
    
    152
    +
    
    153
    +    -- body :: (x |> cv) ~# (x |> cv)
    
    154
    +    body = mkNomReflCo (mkCastTy (mkTyVarTy x) (mkCoVarCo cv))
    
    155
    +
    
    156
    +    -- g :: (forall (cv  :: k1 ~# Type). x |> cv) 
    
    157
    +    --   ~# (forall (cv' :: k2 ~# Type). x |> (sel 2 hk ; cv' ; sym (sel 3 hk)))
    
    158
    +    g   = mkForAllCo cv vis vis (MCo hk) body
    
    159
    +    vis = coreTyLamForAllTyFlag
    
    160
    +
    
    161
    +    -- two distinct coercions to instantiate at
    
    162
    +    coL = mkCoVarCo (mkCv 16 "coL" (mkNomEqPred k1 liftedTypeKind))
    
    163
    +    coR = mkCoVarCo (mkCv 17 "coR" (mkNomEqPred k2 liftedTypeKind))
    
    164
    +    arg = mkCoVarCo (mkCv 18 "arg" (mkNomEqPred (CoercionTy coL) (CoercionTy coR)))
    
    165
    +
    
    166
    +--------------------------------------------------------------------------------
    
    167
    +-- Test runner
    
    168
    +--------------------------------------------------------------------------------
    
    169
    +
    
    170
    +test_cos :: [ ( String, Coercion ) ]
    
    171
    +test_cos =
    
    172
    +  [ ( "InstCo (TyVar)", co1 )
    
    173
    +  , ( "InstCo (CoVar)", co2 )
    
    174
    +  ]
    
    175
    +
    
    176
    +opt_co :: Coercion -> Coercion
    
    177
    +opt_co co =
    
    178
    +  let in_scope = mkInScopeSet $ tyCoVarsOfCo co
    
    179
    +  in
    
    180
    +    optCoercion
    
    181
    +      ( OptCoercionOpts { optCoercionEnabled = True } )
    
    182
    +      ( mkEmptySubst in_scope )
    
    183
    +      co
    
    184
    +
    
    185
    +main :: IO ()
    
    186
    +main = do
    
    187
    +  [libdir] <- getArgs
    
    188
    +  runGhc ( Just libdir ) do
    
    189
    +    dflags <- getSessionDynFlags
    
    190
    +    logger <- getLogger
    
    191
    +    liftIO $ for_ test_cos \ ( name, co ) -> do
    
    192
    +      -- Optimise each test coercion and run the result through Core Lint.
    
    193
    +      let
    
    194
    +        co'      = opt_co co
    
    195
    +        in_scope = tyCoVarsOfCoList co'
    
    196
    +        lint_cfg = initLintConfig dflags in_scope
    
    197
    +      for_ ( lintExpr lint_cfg ( Coercion co' ) ) \ errs ->
    
    198
    +        putMsg logger $
    
    199
    +          vcat [ text "Core Lint error for" <+> text name
    
    200
    +               , pprMessageBag errs
    
    201
    +               ]

  • testsuite/tests/corelint/all.T
    ... ... @@ -9,5 +9,6 @@ setTestOpts(extra_hc_opts('-package ghc'))
    9 9
     setTestOpts(extra_run_opts('"' + config.libdir + '"'))
    
    10 10
     
    
    11 11
     test('LintEtaExpand', normal, compile_and_run, [''])
    
    12
    +test('T27374', normal, compile_and_run, [''])
    
    12 13
     ## These tests use the GHC API.
    
    13 14
     ## Test cases which don't use the GHC API should be added nearer the top.