packages feed

haskell-tools-builtin-refactorings-1.0.0.2: Language/Haskell/Tools/Refactor/Builtin/ExtensionOrganizer/Checkers/FlexibleInstancesChecker.hs

{-# LANGUAGE FlexibleContexts, MultiWayIf, TypeFamilies #-}

module Language.Haskell.Tools.Refactor.Builtin.ExtensionOrganizer.Checkers.FlexibleInstancesChecker where

import Control.Reference ((^.), (!~), biplateRef)
import Language.Haskell.Tools.Refactor as Refact
import Language.Haskell.Tools.Refactor.Builtin.ExtensionOrganizer.ExtMonad
import Language.Haskell.Tools.Refactor.Builtin.ExtensionOrganizer.Utils.TypeLookup (lookupSynDefM)


import Control.Monad.Trans.Maybe (MaybeT(..))
import Data.Data (Data(..))
import Data.List (nub)

import Name as GHC (isTyVarName, isTyConName, isWiredInName)

{-# ANN module "HLint: ignore Redundant bracket" #-}

-- TODO: write "deriving instance ..." tests (should work)
-- TODO: should expand type synonyms  !!!

-- NOTE: Here we implicitly constrained the type with ExtDomain.
--       but we only really need HasNameInfo.

-- NOTE: We need Decl level checking, in order to distinguish
--       class instances from data and type family instances.

chkFlexibleInstances :: CheckNode Decl
chkFlexibleInstances = conditional chkFlexibleInstances' FlexibleInstances

chkFlexibleInstances' :: CheckNode Decl
chkFlexibleInstances' d@(Refact.StandaloneDeriving _ _ rule) = checkedReturn rule d
chkFlexibleInstances' d@(InstanceDecl rule _)                = checkedReturn rule d
chkFlexibleInstances' d = return d

checkedReturn :: InstanceRule -> a -> ExtMonad a
checkedReturn rule x = chkInstanceRule rule >> return x

-- this check DOES transform the AST for its internal computations
-- but returns the original one in the end
-- NOTE: There are two traversals:
--       First one on the class level, and the second one one on the type level.
--       Since biplateRef is lazy, it won't go down to the type level in the first traversal
chkInstanceRule :: CheckNode InstanceRule
chkInstanceRule r@(InstanceRule _ _ ihead) = do
  chkInstanceHead ihead
  return $! r
chkInstanceRule r = return r

refact ::
     (Data.Data.Data (node dom stage), Data.Data.Data (inner dom stage),
      Monad m) =>
     (inner dom stage -> m (inner dom stage))
     -> node dom stage -> m (node dom stage)
refact op = biplateRef !~ op


-- one IHApp will only check its own tyvars (their structure and uniqueness)
-- thus with MultiParamTypeclasses each param will be checked independently
-- (so the same type variable can appear in multiple params)
chkInstanceHead :: CheckNode InstanceHead
chkInstanceHead x@(InfixInstanceHead tyvars _) = do
  tyvars' <- refact rmTypeMisc tyvars
  chkTyVars tyvars'
  addOccurence_ MultiParamTypeClasses x
  addOccurence_ TypeOperators x
  return x
chkInstanceHead app@(AppInstanceHead f tyvars) = do
  tyvars' <- refact rmTypeMisc tyvars
  chkTyVars tyvars'
  case f of
    AppInstanceHead _ _ -> addOccurence_ MultiParamTypeClasses app
    _ -> return ()
  chkInstanceHead f
  return app
chkInstanceHead x@(ParenInstanceHead h) = do
  chkInstanceHead h
  return x
chkInstanceHead app = return app

-- TODO: skip other unnecessary parts of the AST (eg.: UType ctors)
-- where can UTyPromoted appear?
-- can i write forall in instance heads?
-- unboxed tuple (has different kind, can't use in ihead), par array?
-- TH ctors
-- other misc ...
-- synonym expansion (runMaybeT . lookupSynDefM $ vars) (now: if synonym, keep FC)
chkTyVars :: CheckNode Type
chkTyVars vars = do
  msyn <- runMaybeT . lookupSynDefM $ vars
  maybe (performCheck vars) (const $ addOccurence FlexibleInstances vars) msyn

  where performCheck vars = do
          (isOk, (_, vs)) <- runStateT (runMaybeT (chkAll vars)) ([],[])
          case isOk of
            Just isOk ->
              unless (isOk && length vs == (length . nub $ vs)) --tyvars are different
                (addOccurence_ FlexibleInstances vars)
            Nothing   -> error "chkTyVars: Couldn't look up something"
          return vars

        chkAll x =
          ifM (chkTopLevel x) $
            chkOnlyApp x

        chkTopLevel x = -- NOTE: this resembles a monadic bind ... (Cont?)
          ifM (chkListType x) .
            ifM (chkTupleType x) .
              ifM (chkUnitTyCon x) $
                return False

        ifM cond f = do b <- cond; if b then (return b) else f

        chkUnitTyCon (VarType x) = do
          sname <- tyVarSemNameM x
          -- standalone top-level type variables are not accepted
          -- NOTE: -XHaskell98 operator type variables??
          -- NOTE VarType is either TyCon or TyVar
          --      if it is a TyCon, it cannot be wired in (Int, Char, etc)
          if | isTyVarName   sname -> addTyVarM x >> return False
             | isWiredInName sname -> addTyConM x >> return False
             | isTyConName   sname -> addTyConM x >> return True
             | otherwise           -> return True -- NEVER
        chkUnitTyCon _ = return False


        chkSingleTyVar (VarType x) = do
          sname <- tyVarSemNameM x
          if (isTyVarName sname)
            then addTyVarM x >> return True
            else addTyConM x >> return False
        chkSingleTyVar _ = return False


        chkTupleType (TupleType args) = do
          let xs  = args ^. annListElems
          bs <- mapM chkSingleTyVar xs
          return $! and bs
        chkTupleType _ = return False

        chkListType (ListType v) = chkSingleTyVar v
        chkListType _            = return False

        chkOnlyApp :: (MonadState ([Name],[Name]) (m1 m2),
                       MonadTrans m1,
                       MonadState ExtMap m2) =>
                       Type -> MaybeT (m1 m2) Bool
        chkOnlyApp (TypeApp f v@(VarType _)) = do
          isTyVar <- chkSingleTyVar v
          if isTyVar
            then case f of
              (VarType c) -> addTyConM c >> return True
              _           -> chkOnlyApp f
            else return False
        chkOnlyApp x@(InfixTypeApp lhs op rhs) = do
          lift . lift $ addOccurence_ TypeOperators x
          addTyConM . mkNormalName $ (op ^. operatorName)
          lOK <- chkSingleTyVar lhs
          rOK <- chkSingleTyVar rhs
          return $! lOK && rOK
        chkOnlyApp _ = return False

        addTyCon  n (ctors, vars) = (n:ctors, vars)
        addTyVar  n (ctors, vars) = (ctors, n:vars)
        addTyConM n               = modify $ addTyCon n
        addTyVarM n               = modify $ addTyVar  n

        tyVarSemNameM x = MaybeT . return . semanticsName $ x ^. simpleName

rmTypeMisc :: CheckNode Type
rmTypeMisc = rmTParens >=> rmTKinded

rmTKinded :: CheckNode Type
rmTKinded kt@(KindedType t _) = addOccurence_ KindSignatures kt >> return t
rmTKinded x                   = return x

-- removes Parentheses from the AST
-- the structure is reserved
rmTParens :: CheckNode Type
rmTParens (ParenType x) = return x
rmTParens x             = return x