packages feed

disco-0.2: src/Disco/Exhaustiveness/TypeInfo.hs

-- |
-- Module      :  Disco.Exhaustiveness.TypeInfo
-- Copyright   :  disco team and contributors
-- Maintainer  :  byorgey@gmail.com
--
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Utilities for converting Disco types into 'Constructors'
-- that the exhaustiveness checker understands, and utilities
-- for working with 'TypedVar's and their names.
module Disco.Exhaustiveness.TypeInfo where

import Control.Monad (replicateM)
import Data.Function (on)
import Data.List ((\\))
import qualified Data.Map as M
import Disco.AST.Typed (ATerm)
import Disco.Effects.Fresh (Fresh, fresh)
import qualified Disco.Types as Ty
import Polysemy
import Unbound.Generics.LocallyNameless (Name, s2n)

newtype TypedVar = TypedVar (Name ATerm, Ty.Type)
  deriving (Show, Ord)

instance Eq TypedVar where
  TypedVar (n1, _) == TypedVar (n2, _) = n1 == n2

getType :: TypedVar -> Ty.Type
getType (TypedVar (_, t)) = t

data DataCon = DataCon
  { dcIdent :: Ident
  , dcTypes :: [Ty.Type]
  }
  deriving (Ord, Show)

-- This is very important, as we have (sometimes recursive) type aliases
-- Equality of dcTypes doesn't measure equality of dataconstructors,
-- because we could have two different aliases for the same type
-- (And we can't just resolve all aliases up front, because they can be recursive)
instance Eq DataCon where
  (==) = (==) `on` dcIdent

-- The Show instance of 'Ident' should never be used
-- for displaying results to the user.
-- Instead, convert to a proper pattern and use the
-- pretty printer, like in Exhaustiveness.hs
data Ident where
  KUnit :: Ident
  KBool :: Bool -> Ident
  KNat :: Integer -> Ident
  KInt :: Integer -> Ident
  KPair :: Ident
  KCons :: Ident
  KNil :: Ident
  KChar :: Char -> Ident
  KLeft :: Ident
  KRight :: Ident
  KUnknown :: Ident
  deriving (Show, Eq, Ord)

-- | 'Finite' constructors are used in the LYG checker
--   'Infinite' constructors are used when reporting
--   examples of uncovered patterns, we only pick out a few of them
data Constructors where
  Finite :: [DataCon] -> Constructors
  Infinite :: [DataCon] -> Constructors

unknown :: DataCon
unknown = DataCon {dcIdent = KUnknown, dcTypes = []}

unit :: DataCon
unit = DataCon {dcIdent = KUnit, dcTypes = []}

bool :: Bool -> DataCon
bool b = DataCon {dcIdent = KBool b, dcTypes = []}

natural :: Integer -> DataCon
natural n = DataCon {dcIdent = KNat n, dcTypes = []}

integer :: Integer -> DataCon
integer z = DataCon {dcIdent = KInt z, dcTypes = []}

char :: Char -> DataCon
char c = DataCon {dcIdent = KChar c, dcTypes = []}

cons :: Ty.Type -> Ty.Type -> DataCon
cons tHead tTail = DataCon {dcIdent = KCons, dcTypes = [tHead, tTail]}

nil :: DataCon
nil = DataCon {dcIdent = KNil, dcTypes = []}

pair :: Ty.Type -> Ty.Type -> DataCon
pair a b = DataCon {dcIdent = KPair, dcTypes = [a, b]}

left :: Ty.Type -> DataCon
left tl = DataCon {dcIdent = KLeft, dcTypes = [tl]}

right :: Ty.Type -> DataCon
right tr = DataCon {dcIdent = KRight, dcTypes = [tr]}

tyDataCons :: Ty.Type -> Ty.TyDefCtx -> Constructors
tyDataCons ty ctx = tyDataConsHelper $ resolveAlias ty ctx

-- Type aliases that would cause infinite recursion here are
-- not possible to construct, so we don't have to worry about that.
-- (aka cyclic type definitions are not allowed in Disco)
resolveAlias :: Ty.Type -> Ty.TyDefCtx -> Ty.Type
resolveAlias (Ty.TyUser name args) ctx = case M.lookup name ctx of
  Nothing -> error $ show ctx ++ "\nType definition not found for: " ++ show name
  Just (Ty.TyDefBody _argNames typeCon) -> resolveAlias (typeCon args) ctx
resolveAlias t _ = t

-- | Assuming type aliases have been resolved, this
--   function converts Disco types into lists of DataCons
--   that are compatible with the LYG checker.
--
--   A list of constructors is 'Infinite' if the only way to fully
--   match against the type is with a wildcard or variable pattern.
--   Otherwise, it is 'Finite'.
--
--   The LYG checker only reads the list of constructors if
--   a type is 'Finite'. From the point of view of the checker,
--   'Infinite' is a synonym for opaque, and the constructors are discarded.
--   The dataconstructors in an `Infinite` list are only
--   used when generating the 3 positive examples of what
--   you haven't matched against.
--   This will probably need to change a bit when bringing
--   exhaustiveness checking to the new arithmetic patterns.
--
--   Notice the last case of this function, which a wildcard handling the types:
--   (_ Ty.:->: _) (Ty.TySet _) (Ty.TyBag _) (Ty.TyVar _)
--   (Ty.TySkolem _) (Ty.TyProp) (Ty.TyMap _ _) (Ty.TyGraph _)
--
--   I believe all of these are impossible to pattern match against
--   with anything other than a wildcard (or variable pattern) in Disco, so they should always
--   be fully covered. But if they are in a pair, for example, Set(Int)*Int,
--   we still need to generate 3 examples of the pair if that Int part isn't covered.
--   So how do we fill the concrete part of Set(Int), (or a generic type "a", or a function, etc.)?
--   I'm calling that 'unknown', and printing an underscore.
--   (Also, I'm using 'Infinite' for reasons metioned above).
tyDataConsHelper :: Ty.Type -> Constructors
tyDataConsHelper (a Ty.:*: b) = Finite [pair a b]
tyDataConsHelper (l Ty.:+: r) = Finite [left l, right r]
tyDataConsHelper t@(Ty.TyList a) = Finite [nil, cons a t]
tyDataConsHelper Ty.TyVoid = Finite []
tyDataConsHelper Ty.TyUnit = Finite [unit]
tyDataConsHelper Ty.TyBool = Finite [bool True, bool False]
tyDataConsHelper Ty.TyN = Infinite $ map natural [0, 1 ..]
-- Many thanks to this answer and its comment for a convenient way to list the integers
-- https://stackoverflow.com/a/9749957
tyDataConsHelper Ty.TyZ = Infinite $ map integer $ 0 : [y | x <- [1 ..], y <- [x, -x]]
tyDataConsHelper Ty.TyF = Infinite []
tyDataConsHelper Ty.TyQ = Infinite []
-- The Char constructors are all unicode characters, but
-- given in a very particular order that I think will
-- make the most sense for students.
-- Many thanks to Dr. Yorgey for mentioning [minBound .. maxBound] and \\
tyDataConsHelper Ty.TyC =
  Infinite $
    map char $
      alphanum ++ (allUnicodeNicelyOrdered \\ alphanum)
 where
  allUnicodeNicelyOrdered = [(toEnum 32) .. (toEnum 126)] ++ [(toEnum 161) .. maxBound] ++ [minBound .. (toEnum 31)] ++ [(toEnum 127) .. (toEnum 160)]
  alphanum = ['a' .. 'z'] ++ ['A' .. 'Z'] ++ ['0' .. '9']
tyDataConsHelper _ = Infinite [unknown]

newName :: (Member Fresh r) => Sem r (Name ATerm)
newName = fresh $ s2n ""

newVar :: (Member Fresh r) => Ty.Type -> Sem r TypedVar
newVar types = do
  names <- newName
  return $ TypedVar (names, types)

newNames :: (Member Fresh r) => Int -> Sem r [Name ATerm]
newNames i = replicateM i newName

newVars :: (Member Fresh r) => [Ty.Type] -> Sem r [TypedVar]
newVars types = do
  names <- newNames (length types)
  return $ zipWith (curry TypedVar) names types