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.