[GHC] #15753: Inconsistent pattern-match warnings when using guards versus case expressions

GHC ghc-devs at haskell.org
Tue Oct 16 13:08:28 UTC 2018


#15753: Inconsistent pattern-match warnings when using guards versus case
expressions
-------------------------------------+-------------------------------------
           Reporter:  RyanGlScott    |             Owner:  (none)
               Type:  bug            |            Status:  new
           Priority:  normal         |         Milestone:  8.8.1
          Component:  Compiler       |           Version:  8.6.1
           Keywords:                 |  Operating System:  Unknown/Multiple
  PatternMatchWarnings               |
       Architecture:                 |   Type of failure:  Incorrect
  Unknown/Multiple                   |  error/warning at compile-time
          Test Case:                 |        Blocked By:
           Blocking:                 |   Related Tickets:
Differential Rev(s):                 |         Wiki Page:
-------------------------------------+-------------------------------------
 Consider the following code (apologies for the rather non-minimal
 example):

 {{{#!hs
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE EmptyCase #-}
 {-# LANGUAGE GADTs #-}
 {-# LANGUAGE PolyKinds #-}
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE TypeOperators #-}
 {-# LANGUAGE UndecidableInstances #-}
 {-# OPTIONS_GHC -Wall -Wno-unticked-promoted-constructors #-}
 module Bug where

 import Data.Kind
 import Data.Type.Bool
 import Data.Type.Equality ((:~:)(..))
 import Data.Void

 data family Sing :: forall k. k -> Type
 data instance Sing :: Bool -> Type where
   SFalse :: Sing False
   STrue  :: Sing True
 data instance Sing :: forall a. [a] -> Type where
   SNil  :: Sing '[]
   SCons :: Sing x -> Sing xs -> Sing (x:xs)
 data instance Sing :: forall a b. (a, b) -> Type where
   STuple2 :: Sing x -> Sing y -> Sing '(x, y)
 newtype instance Sing (f :: k1 ~> k2) =
   SLambda { (@@) :: forall t. Sing t -> Sing (f @@ t) }

 data TyFun :: Type -> Type -> Type
 type a ~> b = TyFun a b -> Type
 infixr 0 ~>
 type family (f :: k1 ~> k2) @@ (x :: k1) :: k2
 infixl 9 @@

 newtype Map k v = MkMap [(k, v)]
 data instance Sing :: forall k v. Map k v -> Type where
   SMkMap :: Sing x -> Sing (MkMap x)

 type family MapEmpty :: Map k v where
   MapEmpty = MkMap '[]

 type family MapInsertWith (f :: v ~> v ~> v) (new_k :: k) (new_v :: v) (m
 :: Map k v) :: Map k v where
   MapInsertWith _ new_k new_v (MkMap '[]) = MkMap '[ '(new_k, new_v)]
   MapInsertWith f new_k new_v (MkMap ('(old_k,old_v):old_kvs)) =
     If (old_k == new_k)
        (MkMap ('(new_k,f @@ new_v @@ old_v):old_kvs))
        (Case (MapInsertWith f new_k new_v (MkMap old_kvs)) old_k old_v)

 type family Case (m :: Map k v) (old_k :: k) (old_v :: v) :: Map k v where
   Case (MkMap kvs) old_k old_v = MkMap ('(old_k,old_v) : kvs)

 sMapInsertWith :: forall k v (f :: v ~> v ~> v) (new_k :: k) (new_v :: v)
 (m :: Map k v).
                   SEq k
                => Sing f -> Sing new_k -> Sing new_v -> Sing m
                -> Sing (MapInsertWith f new_k new_v m)
 sMapInsertWith _  snew_k snew_v (SMkMap SNil) = SMkMap (STuple2 snew_k
 snew_v `SCons` SNil)
 sMapInsertWith sf snew_k snew_v (SMkMap (STuple2 sold_k sold_v `SCons`
 sold_kvs)) =
   case sold_k %== snew_k of
     STrue -> SMkMap (STuple2 snew_k (sf @@ snew_v @@ sold_v) `SCons`
 sold_kvs)
     SFalse ->
       case sMapInsertWith sf snew_k snew_v (SMkMap sold_kvs) of
         SMkMap skvs -> SMkMap (STuple2 sold_k sold_v `SCons` skvs)

 class PEq a where
   type (x :: a) == (y :: a) :: Bool
 class SEq a where
   (%==) :: forall (x :: a) (y :: a).
            Sing x -> Sing y -> Sing (x == y)

 mapInsertWithNonEmpty1 :: forall k v (f :: v ~> v ~> v) (old_k :: k)
 (old_v :: v) (old_kvs :: [(k,v)])
                                      (new_k :: k) (new_v :: v) (m :: Map k
 v).
                           SEq k
                        => Sing f -> Sing new_k -> Sing new_v -> Sing m
                        -> m :~: MkMap ('(old_k,old_v) : old_kvs)
                        -> MapInsertWith f new_k new_v m :~: MapEmpty
                        -> Void
 mapInsertWithNonEmpty1 sf snew_k snew_v (SMkMap sm) Refl Refl
   | STuple2 sold_k _ `SCons` sold_kvs <- sm
   , SFalse <- sold_k %== snew_k
   = case sMapInsertWith sf snew_k snew_v (SMkMap sold_kvs) of {}

 mapInsertWithNonEmpty2 :: forall k v (f :: v ~> v ~> v) (old_k :: k)
 (old_v :: v) (old_kvs :: [(k,v)])
                                      (new_k :: k) (new_v :: v) (m :: Map k
 v).
                           SEq k
                        => Sing f -> Sing new_k -> Sing new_v -> Sing m
                        -> m :~: MkMap ('(old_k,old_v) : old_kvs)
                        -> MapInsertWith f new_k new_v m :~: MapEmpty
                        -> Void
 mapInsertWithNonEmpty2 sf snew_k snew_v (SMkMap sm) Refl Refl
   | STuple2 sold_k _ `SCons` sold_kvs <- sm
   = case sold_k %== snew_k of
       SFalse ->
         case sMapInsertWith sf snew_k snew_v (SMkMap sold_kvs) of {}
 }}}

 If you compile this with GHC 8.6.1 or later, you'll notice something
 interesting happening with the pattern-match coverage checker warnings:

 {{{
 $ /opt/ghc/8.6.1/bin/ghci Bug.hs
 GHCi, version 8.6.1: http://www.haskell.org/ghc/  :? for help
 Loaded GHCi configuration from /home/rgscott/.ghci
 [1 of 1] Compiling Bug              ( Bug.hs, interpreted )

 Bug.hs:78:1: warning: [-Wincomplete-patterns]
     Pattern match(es) are non-exhaustive
     In an equation for ‘mapInsertWithNonEmpty1’:
         Patterns not matched: _ _ _ (SMkMap _) Refl Refl
    |
 78 | mapInsertWithNonEmpty1 sf snew_k snew_v (SMkMap sm) Refl Refl
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...
 Ok, one module loaded.
 }}}

 `mapInsertWithNonEmpty1` is deemed non-exhaustive, but
 `mapInsertWithNonEmpty2` is deemed exhaustive. However, the code for the
 two functions are functionally identical—the only difference is that
 `mapInsertWithNonEmpty1` uses one more pattern guard than
 `mapInsertWithNonEmpty2` does.

-- 
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/15753>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list