ucsd-progsys / liquidhaskell

Liquid Types For Haskell

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Empty data causes totality error

josedusol opened this issue · comments

Hello!. It seems LH expects me to write a Right case branch in the following function test:

{-@ LIQUID "--reflection" @-}

module Test where

data Zero  -- Uninhabited datatype (or use Void instead)

{-@ reflect test @-}
{-@ test :: (Either a Zero) -> a @-}
test :: Either a Zero -> a
test x = case x of { Left a -> a }       -- bad: totalityError "Pattern match(es) are non-exhaustive"

However it should be impossible to reach that branch. What is missing so LH can infer this information?

Using a type alias fixes for me.

{-@ LIQUID "--reflection" @-}

module Test where

{-@ type TZero = {v:Zero | false } @-}
data Zero  -- Uninhabited datatype (or use Void instead)

{-@ reflect test @-}
{-@ test :: (Either a TZero) -> a @-}
test :: Either a Zero -> a
test x = case x of { Left a -> a }

Though I understand that it would be nice to make the termination checker smarter when a datatype has no data constructors.

Thanks. However introducing a new type like that have some weird interaction when interfacing with other functions. Consider for example:

{-@ reflect inj1 @-}
{-@ inj1 :: a -> Either a b @-}
inj1 :: a -> Either a b
inj1 x = Left x

{-@ reflect testInverse @-}
{-@ testInverse  :: a -> Either a TZero @-}
testInverse :: a -> Either a Zero 
testInverse x = inj1 x                   --  Liquid Type Mismatch: Zero is not a subtype of {VV : Zero | false}

That passes verification for me with the LH version in github. Maybe share the full file?

I also tried with the alternative

 {-@ invariant {v:Zero | false} @-}

and verification also passes.

Using the type alias does not work here under v9.2.5:

{-@ LIQUID "--reflection" @-}

module Test where

{-@ type TZero = {v:Zero | false } @-}
data Zero  -- Uninhabited datatype (or use Void instead)

{-@ reflect test @-}
{-@ test :: (Either a TZero) -> a @-}
test :: Either a Zero -> a
test x = case x of { Left a -> a }

{-@ reflect inj1 @-}
{-@ inj1 :: a -> Either a b @-}
inj1 :: a -> Either a b
inj1 x = Left x

{-@ reflect testInverse @-}
{-@ testInverse  :: a -> Either a TZero @-}
testInverse :: a -> Either a Zero   -- A roundabout way of defining testInverse 
testInverse x = inj1 x              -- Liquid Type Mismatch: Zero is not a subtype of {VV : Zero | false}

Hopefully, defining the invariant directly solves the problem. Thanks

Hi @josedusol -- instead of invariant it is better to use using see this

https://ucsd-progsys.github.io/liquidhaskell/specifications/#invariants

In your example you would add the line

{-@ using (Zero) as  {v:Zero | false } @-}

which effectively tells LH that Zero is uninhabited, after which your code works as is:

http://goto.ucsd.edu:8090/index.html#?demo=permalink%2F1701119184_7659.hs

I'm wondering if this should be documented, or we need to make Liquid Haskell automatically consider empty data types as uninhabited.