packages feed

duet-0.0.1: src/Duet/Infer.hs

{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- | A clear-to-read, well-documented, implementation of a Haskell 98
-- type checker adapted from Typing Haskell In Haskell, by Mark
-- P. Jones.

module Duet.Infer
  (
  -- * Type checker
  -- $type-checker
    typeCheckModule
  , byInst
  , InferException(..)
  -- * Setting up
  , addClass
  , addInstance
  , SpecialTypes(..)
  , ReadException(..)
  -- * Printers
  -- , printTypeSignature
  -- * Types syntax tree
  , Type(..)
  , Kind(..)
  , Scheme(..)
  , TypeSignature(..)
  , TypeVariable(..)
  , Qualified(..)
  , Class(..)
  , Predicate(..)
  , TypeConstructor(..)
  -- * Values syntax tree
  , ImplicitlyTypedBinding(..)
  , ExplicitlyTypedBinding(..)
  , Expression(..)
  , Literal(..)
  , Pattern(..)
  , BindGroup(..)
  , Alternative(..)
  , toScheme
  , typeKind
  , classMethodScheme
  ) where

import           Control.Arrow (first,second)
import           Control.Monad.Catch
import           Control.Monad.State
import           Data.Generics
import           Data.Graph
import           Data.List
import           Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import           Data.Maybe
import           Duet.Types

--------------------------------------------------------------------------------
-- Type inference
--

-- $type-checker
--
-- The type checker takes a module and produces a list of type
-- signatures. It checks that all types unify, and infers the types of
-- unannotated expressions. It resolves type-class instances.

-- | Type check the given module and produce a list of type
-- signatures.
--
-- >>> fmap (map printTypeSignature) (typeCheckModule mempty [] [BindGroup [] [[ImplicitlyTypedBinding (Identifier "id") [Alternative [VariablePattern (Identifier "x")] (VariableExpression (Identifier "x"))]]]])
-- ["id :: forall a0. a0 -> a0"]
--
-- Throws 'InferException' in case of a type error.
typeCheckModule ::
     (MonadThrow m)
  => Map Name (Class Type Name Location) -- ^ Set of defined type-classes.
  -> [(TypeSignature Type Name Name)] -- ^ Pre-defined type signatures e.g. for built-ins or FFI.
  -> SpecialTypes Name -- ^ Special types that Haskell uses for pattern matching and literals.
  -> [Binding Type Name Location] -- ^ Bindings in the module.
  -> m ( [BindGroup Type Name (TypeSignature Type Name Location)]
       , Map Name (Class Type Name (TypeSignature Type Name Location)))
typeCheckModule ce as specialTypes bgs0 = do
  (bgs, classes) <- runTypeChecker (dependencyAnalysis bgs0)
  pure (bgs, classes)
  where
    runTypeChecker bgs =
      evalStateT
        (runInferT $ do
           instanceBgs <- classMethodsToGroups specialTypes ce
           (ps, _, bgs') <-
             inferSequenceTypes inferBindGroupTypes ce as (bgs ++ instanceBgs)
           s <- InferT (gets inferStateSubstitutions)
           let rs = reduce ce (map (substitutePredicate s) ps)
           s' <- defaultSubst ce [] rs
           let bgsFinal = map (fmap (substituteTypeSignature (s' @@ s))) bgs'
           ce' <- collectMethods bgsFinal ce
           return (bgsFinal, ce'))
        (InferState nullSubst 0 specialTypes)

-- | Sort the list of bindings by order of no-dependencies first
-- followed by things that depend on them. Group bindings that are
-- mutually recursive.
dependencyAnalysis :: Data l => [Binding Type Name l] -> [BindGroup Type Name l]
dependencyAnalysis = map toBindGroup . stronglyConnComp . bindingsGraph
  where
    toBindGroup =
      \case
        AcyclicSCC binding ->
          BindGroup (explicits [binding]) [implicits [binding]]
        CyclicSCC bindings ->
          BindGroup (explicits bindings) [implicits bindings]
    explicits =
      mapMaybe
        (\case
           ExplicitBinding i -> Just i
           _ -> Nothing)
    implicits =
      mapMaybe
        (\case
           ImplicitBinding i -> Just i
           _ -> Nothing)

-- | Make a graph of the bindings with their dependencies.
bindingsGraph :: Data l => [Binding Type Name l] -> [(Binding Type Name l, Name, [Name])]
bindingsGraph =
  map
    (\binding ->
       ( binding
       , bindingIdentifier binding
       , listify
           (\case
              n@ValueName {} -> n /= bindingIdentifier binding
              _ -> False)
           (bindingAlternatives binding)))

collectMethods ::
     forall l m. MonadThrow m
  => [BindGroup Type Name (TypeSignature Type Name l)]
  -> Map Name (Class Type Name l)
  -> m (Map Name (Class Type Name (TypeSignature Type Name l)))
collectMethods binds =
  fmap M.fromList .
  mapM
    (\(name, cls) -> do
       insts <-
         mapM
           (\inst -> do
              methods <-
                mapM
                  collectMethod
                  (M.toList (dictionaryMethods (instanceDictionary inst)))
              pure
                inst
                { instanceDictionary =
                    (instanceDictionary inst)
                    {dictionaryMethods = M.fromList methods}
                })
           (classInstances cls)
       pure (name, cls {classInstances = insts})) .
  M.toList
  where
    collectMethod ::
         (Name, (l, t))
      -> m ( Name
           , ( TypeSignature Type Name l
             , Alternative Type Name (TypeSignature Type Name l)))
    collectMethod (key, (l, _)) =
      case listToMaybe
             (mapMaybe
                (\(BindGroup ex _) ->
                   listToMaybe
                     (mapMaybe
                        (\i ->
                           if fst (explicitlyTypedBindingId i) == key
                             then listToMaybe
                                    (explicitlyTypedBindingAlternatives i)
                             else Nothing)
                        ex))
                binds) of
        Just alt ->
          pure
            ( key
            , ( TypeSignature l (typeSignatureScheme (alternativeLabel alt))
              , alt))
        Nothing -> throwM MissingMethod

classMethodsToGroups
  :: MonadThrow m
  => SpecialTypes Name -> Map Name (Class Type Name l) -> m [BindGroup Type Name l]
classMethodsToGroups specialTypes =
  mapM
    (\class' ->
       BindGroup <$>
       fmap
         concat
         (mapM
            (\inst ->
               sequence
                 (zipWith
                    (\methodScheme (instMethodName, (l, methodAlt)) ->
                       ExplicitlyTypedBinding <$> pure l <*>
                       pure (instMethodName, l) <*>
                       instanceMethodScheme
                         specialTypes
                         class'
                         methodScheme
                         (instancePredicate inst) <*>
                       pure [methodAlt])
                    (M.elems (classMethods class'))
                    (M.toList (dictionaryMethods (instanceDictionary inst)))))
            (classInstances class')) <*>
       pure []) .
  M.elems

instanceMethodScheme
  :: MonadThrow m
  => SpecialTypes Name
  -> Class Type Name l
  -> Scheme Type Name Type
  -> Scheme Type Name (Predicate Type)
  -> m (Scheme Type Name Type)
instanceMethodScheme _specialTypes cls (Forall methodVars0 (Qualified methodPreds methodType0)) _instScheme@(Forall instanceVars0 (Qualified preds (IsIn _ headTypes))) = do
  methodQual <- instantiateQual (Qualified (methodPreds ++ preds) methodType0)
  pure (Forall methodVars methodQual)
  where
    methodVars = filter (not . flip elem (classTypeVariables cls)) (methodVars0 ++ instanceVars0)
    table = zip (classTypeVariables cls) headTypes
    instantiateQual (Qualified ps t) =
      Qualified <$> mapM instantiatePred ps <*> instantiate t
    instantiatePred (IsIn c t) = IsIn c <$> mapM instantiate t
    instantiate =
      \case
        ty@(VariableType tyVar) ->
          case lookup tyVar table of
            Nothing -> pure ty
            Just typ -> pure typ
        ApplicationType a b ->
          ApplicationType <$> instantiate a <*> instantiate b
        typ -> pure typ

classMethodScheme
  :: MonadThrow m
  => Class t Name l -> Scheme Type Name Type -> m (Scheme Type Name Type)
classMethodScheme cls (Forall methodVars (Qualified methodPreds methodType)) = do
  ty' <- pure methodType
  headVars <- mapM (pure . VariableType) (classTypeVariables cls)
  pure
    (Forall
       methodVars
       (Qualified (methodPreds ++ [IsIn (className cls) headVars]) ty'))

--------------------------------------------------------------------------------
-- Substitution

infixr 4 @@
(@@) :: [Substitution Name] -> [Substitution Name] -> [Substitution Name]
s1 @@ s2 = [Substitution u (substituteType s1 t) | (Substitution u t) <- s2] ++ s1

nullSubst :: [Substitution Name]
nullSubst = []

substituteQualified :: [Substitution Name] -> Qualified Type Name (Type Name) -> Qualified Type Name (Type Name)
substituteQualified substitutions (Qualified predicates t) =
  Qualified
    (map (substitutePredicate substitutions) predicates)
    (substituteType substitutions t)

substituteTypeSignature :: [Substitution Name] -> (TypeSignature Type Name l) -> (TypeSignature Type Name l)
substituteTypeSignature substitutions (TypeSignature l scheme) =
    TypeSignature l (substituteInScheme substitutions scheme)
  where substituteInScheme subs' (Forall kinds qualified) =
          Forall kinds (substituteQualified subs' qualified)

substitutePredicate :: [Substitution Name] -> Predicate Type Name -> Predicate Type Name
substitutePredicate substitutions (IsIn identifier types) =
    IsIn identifier (map (substituteType substitutions) types)

substituteType :: [Substitution Name] -> Type Name -> Type Name
substituteType substitutions (VariableType typeVariable) =
    case find ((== typeVariable) . substitutionTypeVariable) substitutions of
      Just substitution -> substitutionType substitution
      Nothing -> VariableType typeVariable
substituteType substitutions (ApplicationType type1 type2) =
    ApplicationType
      (substituteType substitutions type1)
      (substituteType substitutions type2)
substituteType _ typ = typ

--------------------------------------------------------------------------------
-- Type inference

unify :: MonadThrow m => Type Name -> Type Name -> InferT m ()
unify t1 t2 = do
  s <- InferT (gets inferStateSubstitutions)
  u <- unifyTypes (substituteType s t1) (substituteType s t2)
  InferT
    (modify
       (\s' -> s' {inferStateSubstitutions = u @@ inferStateSubstitutions s'}))

newVariableType :: Monad m => Kind -> InferT m (Type Name)
newVariableType k =
  InferT
    (do inferState <- get
        put inferState {inferStateCounter = inferStateCounter inferState + 1}
        return
          (VariableType (TypeVariable (enumId (inferStateCounter inferState)) k)))

inferExplicitlyTypedBindingType
  :: (MonadThrow m, Show l  )
  => Map Name (Class Type Name l)
  -> [TypeSignature Type Name Name]
  -> (ExplicitlyTypedBinding Type Name l)
  -> InferT m ([Predicate Type Name], ExplicitlyTypedBinding Type Name (TypeSignature Type Name l))
inferExplicitlyTypedBindingType ce as (ExplicitlyTypedBinding l (identifier, l') sc alts) = do
  (Qualified qs t) <- freshInst sc
  (ps, alts') <- inferAltTypes ce as alts t
  s <- InferT (gets inferStateSubstitutions)
  let qs' = map (substitutePredicate s) qs
      t' = substituteType s t
      fs =
        getTypeVariablesOf
          getTypeSignatureTypeVariables
          (map (substituteTypeSignature s) as)
      gs = getTypeTypeVariables t' \\ fs
      sc' = quantify gs (Qualified qs' t')
      ps' = filter (not . entail ce qs') (map (substitutePredicate s) ps)
  (ds, rs) <- split ce fs gs ps'
  if not (sc `schemesEquivalent` sc')
    then throwM (ExplicitTypeMismatch sc sc')
    else if not (null rs)
           then throwM ContextTooWeak
           else return
                  ( ds
                  , ExplicitlyTypedBinding
                      (TypeSignature l sc)
                      (identifier, TypeSignature l' sc)
                      sc
                      alts')

-- | Are two type schemes alpha-equivalent?
schemesEquivalent :: Scheme Type Name Type ->  Scheme Type Name Type -> Bool
schemesEquivalent (Forall vs1 q1) (Forall vs2 q2) =
  length vs1 == length vs2 &&
  evalState (goQ q1 q2) (mempty,mempty)
  where
    goQ (Qualified ps1 t1) (Qualified ps2 t2) =
      (&&) <$> fmap and (sequence (zipWith goPred ps1 ps2)) <*> goType t1 t2
    goPred (IsIn x ts1) (IsIn y ts2) =
      ((x == y) &&) <$> fmap and (sequence (zipWith goType ts1 ts2))
    goType (VariableType tv1) (VariableType tv2) = do
      i <- bind fst first tv1
      j <- bind snd second tv2
      pure (i == j)
    goType (ConstructorType c1) (ConstructorType c2) = pure (c1 == c2)
    goType (ApplicationType f1 a1) (ApplicationType f2 a2) =
      (&&) <$> goType f1 f2 <*> goType a1 a2
    goType _ _ = pure False
    bind the upon tv = do
      ctx <- gets the
      case M.lookup tv ctx of
        Nothing -> do
          modify (upon (M.insert tv (M.size ctx)))
          pure (M.size ctx)
        Just j -> pure j

inferImplicitlyTypedBindingsTypes
  :: (MonadThrow m, Show l)
  => Map Name (Class Type Name l)
  -> [(TypeSignature Type Name Name)]
  -> [ImplicitlyTypedBinding Type Name l]
  -> InferT m ([Predicate Type Name], [(TypeSignature Type Name Name)], [ImplicitlyTypedBinding Type Name (TypeSignature Type Name l)])
inferImplicitlyTypedBindingsTypes ce as bs = do
  ts <- mapM (\_ -> newVariableType StarKind) bs
  let is = map (fst . implicitlyTypedBindingId) bs
      scs = map toScheme ts
      as' = zipWith (\x y -> TypeSignature x y) is scs ++ as
  pss0 <-
    sequence
      (zipWith
         (\b t -> inferAltTypes ce as' (implicitlyTypedBindingAlternatives b) t)
         bs
         ts)
  let pss = map fst pss0
      binds' = map snd pss0
  s <- InferT (gets inferStateSubstitutions)
  let ps' = map (substitutePredicate s) (concat pss)
      ts' = map (substituteType s) ts
      fs =
        getTypeVariablesOf
          getTypeSignatureTypeVariables
          (map (substituteTypeSignature s) as)
      vss = map getTypeTypeVariables ts'
      gs = foldr1' union vss \\ fs
  (ds, rs) <- split ce fs (foldr1' intersect vss) ps'
  if restrictImplicitlyTypedBindings bs
    then let gs' = gs \\ getTypeVariablesOf getPredicateTypeVariables rs
             scs' = map (quantify gs' . (Qualified [])) ts'
         in return
              ( ds ++ rs
              , zipWith (\x y -> TypeSignature x y) is scs'
              , zipWith
                  (\(ImplicitlyTypedBinding l (tid, l') _, binds'') scheme ->
                     ImplicitlyTypedBinding
                       (TypeSignature l scheme)
                       (tid, TypeSignature l' scheme)
                       binds'')
                  (zip bs binds')
                  scs')
    else let scs' = map (quantify gs . (Qualified rs)) ts'
         in return
              ( ds
              , zipWith (\x y -> TypeSignature x y) is scs'
              , zipWith
                  (\(ImplicitlyTypedBinding l (tid, l') _, binds'') scheme ->
                     ImplicitlyTypedBinding (TypeSignature l scheme) (tid,TypeSignature l' scheme) binds'')
                  (zip bs binds')
                  scs')
  where
    foldr1' f xs =
      if null xs
        then []
        else foldr1 f xs

inferBindGroupTypes
  :: (MonadThrow m, Show l)
  => Map Name (Class Type Name l)
  -> [(TypeSignature Type Name Name)]
  -> (BindGroup Type Name l)
  -> InferT m ([Predicate Type Name], [(TypeSignature Type Name Name)], BindGroup Type Name (TypeSignature Type Name l))
inferBindGroupTypes ce as (BindGroup es iss) = do
  let as' = [TypeSignature v sc | ExplicitlyTypedBinding _ (v, _) sc _alts <- es]
  (ps, as'', iss') <-
    inferSequenceTypes0 inferImplicitlyTypedBindingsTypes ce (as' ++ as) iss
  qss <- mapM (inferExplicitlyTypedBindingType ce (as'' ++ as' ++ as)) es
  return (ps ++ concat (map fst qss), as'' ++ as', BindGroup (map snd qss) iss')

inferSequenceTypes0
  :: Monad m
  => (Map Name (Class Type Name l) -> [(TypeSignature Type Name Name)] -> [bg l] -> InferT m ([Predicate Type Name], [(TypeSignature Type Name Name)], [bg (TypeSignature Type Name l)]))
  -> Map Name (Class Type Name l)
  -> [(TypeSignature Type Name Name)]
  -> [[bg l]]
  -> InferT m ([Predicate Type Name], [(TypeSignature Type Name Name)], [[bg (TypeSignature Type Name l)]])
inferSequenceTypes0 _ _ _ [] = return ([], [], [])
inferSequenceTypes0 ti ce as (bs:bss) = do
  (ps, as', bs') <- ti ce as bs
  (qs, as'', bss') <- inferSequenceTypes0 ti ce (as' ++ as) bss
  return (ps ++ qs, as'' ++ as', bs' : bss')

inferSequenceTypes
  :: Monad m
  => (Map Name (Class Type Name l) -> [(TypeSignature Type Name Name)] -> bg l -> InferT m ([Predicate Type Name], [(TypeSignature Type Name Name)], bg (TypeSignature Type Name l)))
  -> Map Name (Class Type Name l)
  -> [(TypeSignature Type Name Name)]
  -> [bg l]
  -> InferT m ([Predicate Type Name], [(TypeSignature Type Name Name)], [bg (TypeSignature Type Name l)])
inferSequenceTypes _ _ _ [] = return ([], [], [])
inferSequenceTypes ti ce as (bs:bss) = do
  (ps, as', bs') <- ti ce as bs
  (qs, as'', bss') <- inferSequenceTypes ti ce (as' ++ as) bss
  return (ps ++ qs, as'' ++ as', bs' : bss')

--------------------------------------------------------------------------------
-- Instantiation

instantiateType :: [(TypeVariable Name, Type Name)] -> Type Name -> Type Name
instantiateType ts (ApplicationType l r) =
  ApplicationType (instantiateType ts l) (instantiateType ts r)
instantiateType ts ty@(VariableType tyvar) =
  case lookup tyvar ts of
    Nothing -> ty
    Just ty' -> ty' -- TODO: possibly throw error here?
-- instantiateType ts (GenericType n) = ts !! n
instantiateType _ t = t

instantiateQualified :: [(TypeVariable Name, Type Name)] -> Qualified Type Name (Type Name) -> Qualified Type Name (Type Name)
instantiateQualified ts (Qualified ps t) =
  Qualified (map (instantiatePredicate ts) ps) (instantiateType ts t)

instantiatePredicate :: [(TypeVariable Name, Type Name)] -> Predicate Type Name -> Predicate Type Name
instantiatePredicate ts (IsIn c t) = IsIn c (map (instantiateType ts) t)

--------------------------------------------------------------------------------
-- Type variables

getTypeSignatureTypeVariables :: (TypeSignature Type Name Name) -> [TypeVariable Name]
getTypeSignatureTypeVariables = getTypeVariables where
  getTypeVariables (TypeSignature _  scheme) = getSchemeTypeVariables scheme
    where getSchemeTypeVariables (Forall _ qualified) = getQualifiedTypeVariables qualified

getQualifiedTypeVariables :: Qualified Type Name (Type Name) -> [TypeVariable Name]
getQualifiedTypeVariables = getTypeVariables
  where
    getTypeVariables (Qualified predicates t) =
      getTypeVariablesOf getPredicateTypeVariables predicates `union`
      getTypeTypeVariables t

getPredicateTypeVariables :: Predicate Type Name -> [TypeVariable Name]
getPredicateTypeVariables (IsIn _ types) = getTypeVariablesOf getTypeTypeVariables types

getTypeTypeVariables :: Type Name -> [TypeVariable Name]
getTypeTypeVariables = getTypeVariables where
  getTypeVariables (VariableType typeVariable) = [typeVariable]
  getTypeVariables (ApplicationType type1 type2) =
    getTypeVariables type1 `union` getTypeVariables type2
  getTypeVariables _ = []

getTypeVariablesOf :: (a -> [TypeVariable Name]) -> [a] -> [TypeVariable Name]
getTypeVariablesOf f = nub . concatMap f

-- | Get the kind of a type.
typeKind :: Type Name -> Kind
typeKind (ConstructorType typeConstructor) = typeConstructorKind typeConstructor
typeKind (VariableType typeVariable) = typeVariableKind typeVariable
typeKind (ApplicationType typ _) =
  case (typeKind typ) of
    (FunctionKind _ kind) -> kind
    k -> k

--------------------------------------------------------------------------------
-- GOOD NAMING CONVENInferON, UNSORTED

-- | The monomorphism restriction is invoked when one or more of the
-- entries in a list of implicitly typed bindings is simple, meaning
-- that it has an alternative with no left-hand side patterns. The
-- following function provides a way to test for this:
restrictImplicitlyTypedBindings :: [(ImplicitlyTypedBinding t Name l)] -> Bool
restrictImplicitlyTypedBindings = any simple
  where
    simple =
      any (null . alternativePatterns) . implicitlyTypedBindingAlternatives

-- | The following function calculates the list of ambiguous variables
-- and pairs each one with the list of predicates that must be
-- satisfied by any choice of a default:
ambiguities :: [TypeVariable Name] -> [Predicate Type Name] -> [Ambiguity Name]
ambiguities typeVariables predicates =
  [ Ambiguity typeVariable (filter (elem typeVariable . getPredicateTypeVariables) predicates)
  | typeVariable <- getTypeVariablesOf getPredicateTypeVariables predicates \\ typeVariables
  ]

-- | The unifyTypeVariable function is used for the special case of unifying a
-- variable u with a type t.
unifyTypeVariable :: MonadThrow m => TypeVariable Name -> Type Name -> m [Substitution Name]
unifyTypeVariable typeVariable typ
  | typ == VariableType typeVariable = return nullSubst
  | typeVariable `elem` getTypeTypeVariables typ = throwM OccursCheckFails
  | typeVariableKind typeVariable /= typeKind typ = throwM KindMismatch
  | otherwise = return [Substitution typeVariable typ]

unifyPredicates :: Predicate Type Name -> Predicate Type Name -> Maybe [Substitution Name]
unifyPredicates = lift' unifyTypeList

oneWayMatchPredicate :: Predicate Type Name -> Predicate Type Name -> Maybe [Substitution Name]
oneWayMatchPredicate = lift' oneWayMatchLists

unifyTypes :: MonadThrow m => Type Name -> Type Name -> m [Substitution Name]
unifyTypes (ApplicationType l r) (ApplicationType l' r') = do
              s1 <- unifyTypes l l'
              s2 <- unifyTypes (substituteType s1 r) (substituteType s1 r')
              return (s2 @@ s1)
unifyTypes (VariableType u) t = unifyTypeVariable u t
unifyTypes t (VariableType u) = unifyTypeVariable u t
unifyTypes (ConstructorType tc1) (ConstructorType tc2)
              | tc1 == tc2 = return nullSubst
unifyTypes a b = throwM (TypeMismatch a b)

unifyTypeList :: MonadThrow m => [Type Name] -> [Type Name] -> m [Substitution Name]
unifyTypeList (x:xs) (y:ys) = do
    s1 <- unifyTypes x y
    s2 <- unifyTypeList (map (substituteType s1) xs) (map (substituteType s1) ys)
    return (s2 @@ s1)
unifyTypeList [] [] = return nullSubst
unifyTypeList _ _ = throwM ListsDoNotUnify

oneWayMatchType :: MonadThrow m => Type Name -> Type Name -> m [Substitution Name]
oneWayMatchType (ApplicationType l r) (ApplicationType l' r') = do
  sl <- oneWayMatchType l l'
  sr <- oneWayMatchType r r'
  merge sl sr
oneWayMatchType (VariableType u) t
  | typeVariableKind u == typeKind t = return [Substitution u t]
oneWayMatchType (ConstructorType tc1) (ConstructorType tc2)
  | tc1 == tc2 = return nullSubst
oneWayMatchType _ _ = throwM TypeMismatchOneWay

oneWayMatchLists :: MonadThrow m => [Type Name] -> [Type Name] -> m [Substitution Name]
oneWayMatchLists ts ts' = do
    ss <- sequence (zipWith oneWayMatchType ts ts')
    foldM merge nullSubst ss

--------------------------------------------------------------------------------
-- Garbage

lookupName
  :: MonadThrow m
  => Name -> [(TypeSignature Type Name Name)] -> m (Scheme Type Name Type)
lookupName name cands = go name cands where
  go n [] = throwM (NotInScope cands n)
  go i ((TypeSignature i'  sc):as) =
    if i == i'
      then return sc
      else go i as

enumId :: Int -> Name
enumId n = ForallName n

inferLiteralType
  :: Monad m
  => SpecialTypes Name -> Literal -> InferT m ([Predicate Type Name], Type Name)
inferLiteralType specialTypes (CharacterLiteral _) =
  return ([], ConstructorType (specialTypesChar specialTypes))
inferLiteralType specialTypes (IntegerLiteral _) = do
  return ([], ConstructorType (specialTypesInteger specialTypes))
inferLiteralType specialTypes (StringLiteral _) =
  return ([], ConstructorType (specialTypesString specialTypes))
inferLiteralType specialTypes (RationalLiteral _) = do
  return ([], ConstructorType (specialTypesRational specialTypes))

inferPattern
  :: MonadThrow m
  => [TypeSignature Type Name Name] -> Pattern Type Name l
  -> InferT m (Pattern Type Name (TypeSignature Type Name l), [Predicate Type Name], [(TypeSignature Type Name Name)], Type Name)
inferPattern signatures = go
  where
    go (BangPattern p) = do
      (p', x, y, z) <- go p
      pure (BangPattern p', x, y, z)
    go (VariablePattern l i) = do
      v <- newVariableType StarKind
      return
        ( VariablePattern (TypeSignature l (toScheme v)) i
        , []
        , [TypeSignature i (toScheme v)]
        , v)
    go (WildcardPattern l s) = do
      v <- newVariableType StarKind
      return (WildcardPattern (TypeSignature l (toScheme v)) s, [], [], v)
    go (AsPattern l i pat) = do
      (pat', ps, as, t) <- go pat
      return
        ( AsPattern (TypeSignature l (toScheme t)) i pat'
        , ps
        , (TypeSignature i (toScheme t)) : as
        , t)
    go (LiteralPattern l0 l) = do
      specialTypes <- InferT (gets inferStateSpecialTypes)
      (ps, t) <- inferLiteralType specialTypes l
      return (LiteralPattern (TypeSignature l0 (toScheme t)) l, ps, [], t)
    go (ConstructorPattern l i pats) = do
      TypeSignature _ sc <- substituteConstr signatures i
      (pats', ps, as, ts) <- inferPatterns signatures pats
      t' <- newVariableType StarKind
      (Qualified qs t) <- freshInst sc
      specialTypes <- InferT (gets inferStateSpecialTypes)
      let makeArrow :: Type Name -> Type Name -> Type Name
          a `makeArrow` b =
            ApplicationType
              (ApplicationType
                 (ConstructorType (specialTypesFunction specialTypes))
                 a)
              b
      unify t (foldr makeArrow t' ts)
      return
        ( ConstructorPattern (TypeSignature l (toScheme t')) i pats'
        , ps ++ qs
        , as
        , t')
-- inferPattern (LazyPattern pat) = inferPattern pat

substituteConstr
  :: MonadThrow m
  => [TypeSignature Type Name Name] -> Name -> m (TypeSignature Type Name Name)
substituteConstr subs i =
  case find
         (\case
            TypeSignature i' _ -> i' == i)
         subs of
    Just sig -> pure sig
    _ ->
      throwM
        (NameNotInConScope
           (filter
              (\case
                 TypeSignature (ConstructorName _ _) _ -> True
                 _ -> False)
              subs)
           i)

inferPatterns
  :: MonadThrow m
  => [TypeSignature Type Name Name] -> [Pattern Type Name l] -> InferT m ([Pattern Type Name (TypeSignature Type Name l)], [Predicate Type Name], [(TypeSignature Type Name Name)], [Type Name])
inferPatterns ss pats = do
  psasts <- mapM (inferPattern ss) pats
  let ps = concat [ps' | (_,ps', _, _) <- psasts]
      as = concat [as' | (_,_, as', _) <- psasts]
      ts = [t | (_, _, _, t) <- psasts]
      pats' = [ p | (p,_,_,_) <- psasts]
  return (pats', ps, as, ts)

predHead :: Predicate Type Name -> Name
predHead (IsIn i _) = i

lift'
  :: MonadThrow m
  => ([Type Name] -> [Type Name] -> m a) -> Predicate Type Name -> Predicate Type Name -> m a
lift' m (IsIn i ts) (IsIn i' ts')
  | i == i' = m ts ts'
  | otherwise = throwM ClassMismatch

-- lookupClassTypeVariables :: Map Name (Class Type Name l) -> Name -> [TypeVariable Name]
-- lookupClassTypeVariables ce i =
--   fromMaybe
--     []
--     (fmap classTypeVariables (M.lookup i ce))

-- lookupClassSuperclasses :: Map Name (Class Type Name l) -> Name -> [Predicate Type Name]
-- lookupClassSuperclasses ce i = maybe [] classSuperclasses (M.lookup i ce)

-- lookupClassMethods :: Map Name (Class Type Name l) -> Name -> Map Name (Type Name)
-- lookupClassMethods ce i = maybe mempty classMethods (M.lookup i ce)

-- lookupClassInstances :: Map Name (Class Type Name l) -> Name -> [Instance Type Name l]
-- lookupClassInstances ce i =
--   maybe [] classInstances (M.lookup i ce)

defined :: Maybe a -> Bool
defined (Just _) = True
defined Nothing = False


-- | Add a class to the environment. Example:
--
-- @
-- env <- addClass (Name l \"Num\") [TypeVariable (Name \"n\") StarKind] [] mempty
-- @
--
-- Throws 'ReadException' in the case of error.
addClass
  :: MonadThrow m
  => Class Type Name l
  -> Map Name (Class Type Name l)
  -> m (Map Name (Class Type Name l))
addClass (Class vs ps _ i methods) ce
  | defined (M.lookup i ce) = throwM ClassAlreadyDefined
  | any (not . defined . flip M.lookup ce . predHead) ps =
    throwM UndefinedSuperclass
  | otherwise = return (M.insert i (Class vs ps [] i methods) ce)


-- | Add an instance of a class. Example:
--
-- @
-- env <- addInstance [] (IsIn (Name \"Num\") [ConstructorType (TypeConstructor (Name \"Integer\") StarKind)]) mempty
-- @
--
-- Throws 'ReadException' in the case of error.
addInstance
  :: MonadThrow m
  => Instance Type Name l
  -> Map Name (Class Type Name l)
  -> m (Map Name (Class Type Name l))
addInstance (Instance (Forall vs (Qualified preds p@(IsIn i _))) dict) ce =
  case M.lookup i ce of
    Nothing -> throwM NoSuchClassForInstance
    Just typeClass
      | any (overlap p) qs -> throwM OverlappingInstance
      | otherwise -> return (M.insert i c ce)
      where its = classInstances typeClass
            qs = [q | Instance (Forall _ (Qualified _ q)) _ <- its]
            ps = []
            c =
              (Class
                 (classTypeVariables typeClass)
                 (classSuperclasses typeClass)
                 (Instance (Forall vs (Qualified (nub (ps ++ preds)) p)) dict :
                  its)
                 i
                 (classMethods typeClass))

overlap :: Predicate Type Name -> Predicate Type Name -> Bool
overlap p q = defined (unifyPredicates p q)

bySuper :: Map Name (Class Type Name l) -> Predicate Type Name -> [Predicate Type Name]
bySuper ce p@(IsIn i ts) = p : concat (map (bySuper ce) supers)
  where
    supers =
      map
        (substitutePredicate substitutions)
        (maybe [] classSuperclasses (M.lookup i ce))
    substitutions =
      zipWith Substitution (maybe [] classTypeVariables (M.lookup i ce)) ts

byInst
  :: Map Name (Class Type Name l)
  -> Predicate Type Name
  -> Maybe ([Predicate Type Name], Dictionary Type Name l)
byInst ce p@(IsIn i _) =
  case M.lookup i ce of
    Nothing -> throwM NoSuchClassForInstance
    Just typeClass ->
      (msum [tryInst it | it <- classInstances typeClass])
  where
    tryInst (Instance (Forall _ (Qualified ps h)) dict) = do
      (return ())
      case oneWayMatchPredicate h p of
        Just u ->
          (Just (map (substitutePredicate u) ps, dict))
        Nothing -> Nothing

entail :: Show l =>  Map Name (Class Type Name l) -> [Predicate Type Name] -> Predicate Type Name -> Bool
entail ce ps p =
  any (p `elem`) (map (bySuper ce) ps) ||
  case byInst ce p of
    Nothing -> False
    Just (qs, _) -> all (entail ce ps) qs

simplify :: ([Predicate Type Name] -> Predicate Type Name -> Bool) -> [Predicate Type Name] -> [Predicate Type Name]
simplify ent = loop []
  where
    loop rs [] = rs
    loop rs (p:ps)
      | ent (rs ++ ps) p = loop rs ps
      | otherwise = loop (p : rs) ps

reduce :: Show l => Map Name (Class Type Name l) -> [Predicate Type Name] -> [Predicate Type Name]
reduce ce = simplify (scEntail ce) . elimTauts ce

elimTauts :: Show l => Map Name (Class Type Name l) -> [Predicate Type Name] -> [Predicate Type Name]
elimTauts ce ps = [p | p <- ps, not (entail ce [] p)]

scEntail :: Map Name (Class Type Name l) -> [Predicate Type Name] -> Predicate Type Name -> Bool
scEntail ce ps p = any (p `elem`) (map (bySuper ce) ps)

quantify :: [TypeVariable Name] -> Qualified Type Name (Type Name) -> Scheme Type Name Type
quantify vs qt = Forall vs' qt
  where
    vs' = [v | v <- getQualifiedTypeVariables qt, v `elem` vs]
    {-ks = map typeVariableKind vs'-}
    {-s = zipWith Substitution vs' (map undefined {-GenericType-} [0 ..])-}

toScheme :: Type Name -> Scheme Type Name Type
toScheme t = Forall [] (Qualified [] t)

merge
  :: MonadThrow m
  => [Substitution Name] -> [Substitution Name] -> m [Substitution Name]
merge s1 s2 =
  if agree
    then return (s1 ++ s2)
    else throwM MergeFail
  where
    agree =
      all
        (\v -> substituteType s1 (VariableType v) == substituteType s2 (VariableType v))
        (map substitutionTypeVariable s1 `intersect`
         map substitutionTypeVariable s2)

inferExpressionType
  :: (MonadThrow m, Show l)
  => Map Name (Class Type Name l)
  -> [(TypeSignature Type Name Name)]
  -> (Expression Type Name l)
  -> InferT m ([Predicate Type Name], Type Name, Expression Type Name (TypeSignature Type Name l))
inferExpressionType ce as (ParensExpression l e) = do
  (ps, t, e') <- inferExpressionType ce as e
  pure (ps, t, ParensExpression (fmap (const l) (expressionLabel e')) e')
inferExpressionType _ as (VariableExpression l i) = do
  sc <- lookupName i as
  qualified@(Qualified ps t) <- freshInst sc
  let scheme = (Forall [] qualified)
  return (ps, t, VariableExpression (TypeSignature l scheme) i)
inferExpressionType _ _ (ConstantExpression l i) = do
  t <- newVariableType StarKind
  return ([], t, (ConstantExpression (TypeSignature l (toScheme t)) i))
inferExpressionType _ as (ConstructorExpression l i) = do
  sc <- lookupName i as
  qualified@(Qualified ps t) <- freshInst sc
  let scheme = (Forall [] qualified)
  return (ps, t, ConstructorExpression (TypeSignature l scheme) i)
inferExpressionType _ _ (LiteralExpression l0 l) = do
  specialTypes <- InferT (gets inferStateSpecialTypes)
  (ps, t) <- inferLiteralType specialTypes l
  let scheme = (Forall [] (Qualified ps t))
  return (ps, t, LiteralExpression (TypeSignature l0 scheme) l)
inferExpressionType ce as (ApplicationExpression l e f) = do
  (ps, te, e') <- inferExpressionType ce as e
  (qs, tf, f') <- inferExpressionType ce as f
  t <- newVariableType StarKind
  specialTypes <- InferT (gets inferStateSpecialTypes)
  let makeArrow :: Type Name -> Type  Name -> Type  Name
      a `makeArrow` b = ApplicationType (ApplicationType (ConstructorType(specialTypesFunction specialTypes)) a) b
  unify (tf `makeArrow` t) te
  let scheme = (Forall [] (Qualified (ps++qs) t))
  return (ps ++ qs, t, ApplicationExpression (TypeSignature l scheme) e' f')
inferExpressionType ce as (InfixExpression l x (i,op) y) = do
  (ps, ts, ApplicationExpression l' (ApplicationExpression _ (op') x') y') <-
    inferExpressionType
      ce
      as
      (ApplicationExpression l (ApplicationExpression l op x) y)
  pure (ps, ts, InfixExpression l' x' (i, op') y')
inferExpressionType ce as (LetExpression l bg e) = do
  (ps, as', bg') <- inferBindGroupTypes ce as bg
  (qs, t, e') <- inferExpressionType ce (as' ++ as) e
  let scheme = (Forall [] (Qualified (ps++qs) t))
  return (ps ++ qs, t, LetExpression (TypeSignature l scheme) bg' e')
inferExpressionType ce as (LambdaExpression l alt) = do
  (x, y, s) <- inferAltTypeForLambda ce as alt
  pure
    ( x
    , y
    , LambdaExpression
        (TypeSignature l (typeSignatureScheme (alternativeLabel s)))
        s)
inferExpressionType ce as (IfExpression l e e1 e2) = do
  (ps, t, e') <- inferExpressionType ce as e
  specialTypes <- InferT (gets inferStateSpecialTypes)
  unify t (dataTypeConstructor (specialTypesBool specialTypes))
  (ps1, t1, e1') <- inferExpressionType ce as e1
  (ps2, t2, e2') <- inferExpressionType ce as e2
  unify t1 t2
  let scheme = (Forall [] (Qualified (ps ++ ps1 ++ ps2) t1))
  return (ps ++ ps1 ++ ps2, t1, IfExpression (TypeSignature l scheme) e' e1' e2')
inferExpressionType ce as (CaseExpression l e branches) = do
  (ps0, t, e') <- inferExpressionType ce as e
  v <- newVariableType StarKind
  let tiBr (CaseAlt l' pat f) = do
        (pat', ps, as', t') <- inferPattern as pat
        unify t t'
        (qs, t'', f') <- inferExpressionType ce (as' ++ as) f
        unify v t''
        return
          (ps ++ qs, (CaseAlt (fmap (const l') (expressionLabel f')) pat' f'))
  branchs <- mapM tiBr branches
  let pss = map fst branchs
      branches' = map snd branchs
  let scheme = (Forall [] (Qualified (ps0 ++ concat pss) v))
  return
    (ps0 ++ concat pss, v, CaseExpression (TypeSignature l scheme) e' branches')

inferAltTypeForLambda
  :: (MonadThrow m, Show l)
  => Map Name (Class Type Name l)
  -> [(TypeSignature Type Name Name)]
  -> Alternative Type Name l
  -> InferT m ([Predicate Type Name], Type Name, Alternative Type Name (TypeSignature Type Name l))
inferAltTypeForLambda ce as alt =
  inferAltType0
    ce
    as
    (\l scheme pats ex -> Alternative (TypeSignature l scheme) pats ex)
    alt

inferAltTypeForBind
  :: (MonadThrow m, Show l)
  => Map Name (Class Type Name l)
  -> [(TypeSignature Type Name Name)]
  -> Alternative Type Name l
  -> InferT m ([Predicate Type Name], Type Name, Alternative Type Name (TypeSignature Type Name l))
inferAltTypeForBind ce as alt =
  inferAltType0 ce as makeAltForDecl alt

inferAltType0
  :: (Show t1, MonadThrow m)
  => Map Name (Class Type Name t1)
  -> [TypeSignature Type Name Name]
  -> (t1 -> Scheme Type Name Type -> [Pattern Type Name (TypeSignature Type Name t1)] -> Expression Type Name (TypeSignature Type Name t1) -> t)
  -> Alternative Type Name t1
  -> InferT m ([Predicate Type Name], Type Name, t)
inferAltType0 ce as makeAlt (Alternative l pats e) = do
  (pats', ps, as', ts) <- inferPatterns as pats
  (qs, t, e') <- inferExpressionType ce (as' ++ as) e
  specialTypes <- InferT (gets inferStateSpecialTypes)
  let makeArrow :: Type Name -> Type Name -> Type Name
      a `makeArrow` b = ApplicationType (ApplicationType (ConstructorType(specialTypesFunction specialTypes)) a) b
  let scheme = (Forall [] (Qualified (ps ++ qs) (foldr makeArrow t ts)))
  return (ps ++ qs, foldr makeArrow t ts, makeAlt l scheme pats' e')

-- | During parsing, we parse
-- f = \x -> x
-- as
-- f x = x
-- After type-checking, we expand the lambda out again:
--
-- f = \x -> x
--
-- But type-checked and generalized.
makeAltForDecl
  :: a
  -> Scheme Type i1 Type
  -> [Pattern Type i (TypeSignature Type i1 a)]
  -> Expression Type i (TypeSignature Type i1 a)
  -> Alternative Type i (TypeSignature Type i1 a)
makeAltForDecl l scheme pats' e' =
  if null pats'
    then Alternative (TypeSignature l scheme) pats' e'
    else Alternative
           (TypeSignature l scheme)
           []
           (LambdaExpression
              (TypeSignature l scheme)
              (Alternative (TypeSignature l scheme) pats' e'))

inferAltTypes
  :: (MonadThrow m, Show l)
  => Map Name (Class Type Name l)
  -> [(TypeSignature Type Name Name)]
  -> [Alternative Type Name l]
  -> Type Name
  -> InferT m ([Predicate Type Name], [Alternative Type Name (TypeSignature Type Name l)])
inferAltTypes ce as alts t = do
  psts <- mapM (inferAltTypeForBind ce as) alts
  mapM_ (unify t) (map snd3 psts)
  return (concat (map fst3 psts), map thd3 psts)
  where snd3 (_,x,_) = x
        thd3 (_,_,x) = x
        fst3 (x,_,_) = x

split
  :: (MonadThrow m, Show l)
  => Map Name (Class Type Name l) -> [TypeVariable Name] -> [TypeVariable Name] -> [Predicate Type Name] -> m ([Predicate Type Name], [Predicate Type Name])
split ce fs gs ps = do
  let ps' = reduce ce ps
      (ds, rs) = partition (all (`elem` fs) . getPredicateTypeVariables) ps'
  rs' <- defaultedPredicates ce (fs ++ gs) rs
  return (ds, rs \\ rs')

candidates :: (Show l)=> Map Name (Class Type Name l) -> Ambiguity Name -> [Type Name]
candidates ce (Ambiguity v qs) =
  [ t'
  | let is = [i | IsIn i _ <- qs]
        ts = [t | IsIn _ t <- qs]
  , all ([VariableType v] ==) ts
  , any (`elem` numClasses) is
  , all (`elem` stdClasses) is
  , t' <- [VariableType (TypeVariable (TypeName (-1) "x") StarKind)]-- classEnvironmentDefaults ce
  , all (entail ce []) [IsIn i [t'] | i <- is]
  ]
  where -- disabling these
        numClasses = [ForallName (-1)]
        stdClasses = [ForallName (-1)]


withDefaults
  :: (MonadThrow m, Show l)
  => String
  -> ([Ambiguity Name] -> [Type Name] -> a)
  -> Map Name (Class Type Name l)
  -> [TypeVariable Name]
  -> [Predicate Type Name]
  -> m a
withDefaults _label f ce vs ps
  | any null tss = throwM (AmbiguousInstance vps)
  | otherwise = do
    return (f vps (map head tss))
  where
    -- showp :: Show a => a -> String
    -- showp = \x -> "(" ++ show x ++ ")"
    vps = ambiguities vs ps
    tss = map (candidates ce) vps

defaultedPredicates
  :: (MonadThrow m, Show l)
  => Map Name (Class Type Name l) -> [TypeVariable Name] -> [Predicate Type Name] -> m [Predicate Type Name]
defaultedPredicates = withDefaults "defaultedPredicates" (\vps _ -> concat (map ambiguityPredicates vps))

defaultSubst
  :: (MonadThrow m, Show l)
  => Map Name (Class Type Name l) -> [TypeVariable Name] -> [Predicate Type Name] -> m [Substitution Name]
defaultSubst = withDefaults "defaultSubst" (\vps ts -> zipWith Substitution (map ambiguityTypeVariable vps) ts)

-- extSubst
--   :: Monad m
--   => [Substitution] -> InferT m ()
-- extSubst s' =
--   InferT
--     (modify
--        (\s -> s {inferStateSubstitutions = s' @@ inferStateSubstitutions s}))

freshInst
  :: Monad m
  => Scheme Type Name Type -> InferT m (Qualified Type Name (Type Name))
freshInst (Forall ks qt) = do
  ts <- mapM (\vorig -> (vorig, ) <$> newVariableType (typeVariableKind vorig)) ks
  return (instantiateQualified ts qt)