{-# LANGUAGE LambdaCase, PatternGuards, ViewPatterns #-}
module Hint.Lambda(lambdaHint) where
import Hint.Type (DeclHint, Idea, Note(RequiresExtension), suggest, warn, toSS, suggestN, ideaNote, substVars)
import Util
import Data.List.Extra
import Data.Set (Set)
import qualified Data.Set as Set
import Refact.Types hiding (RType(Match))
import Data.Generics.Uniplate.DataOnly (universe, universeBi, transformBi)
import BasicTypes
import GHC.Hs
import OccName
import RdrName
import SrcLoc
import Language.Haskell.GhclibParserEx.GHC.Hs.Expr (isTypeApp, isOpApp, isLambda, isQuasiQuote, isVar, isDol, strToVar)
import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable
import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader
import GHC.Util.Brackets (isAtom)
import GHC.Util.FreeVars (free, allVars, freeVars, pvars, vars, varss)
import GHC.Util.HsExpr (allowLeftSection, allowRightSection, niceLambdaR, lambda)
import GHC.Util.View
lambdaHint :: DeclHint
lambdaHint _ _ x
= concatMap (uncurry lambdaExp) (universeParentBi x)
++ concatMap lambdaDecl (universe x)
lambdaDecl :: LHsDecl GhcPs -> [Idea]
lambdaDecl
o@(L _ (ValD _
origBind@FunBind {fun_id = funName@(L loc1 _), fun_matches =
MG {mg_alts =
L _ [L _ (Match _ ctxt@(FunRhs _ Prefix _) pats (GRHSs _ [L _ (GRHS _ [] origBody@(L loc2 _))] bind))]}}))
| L _ (EmptyLocalBinds noExtField) <- bind
, isLambda $ fromParen origBody
, null (universeBi pats :: [HsExpr GhcPs])
= let (newPats, newBody) = fromLambda . lambda pats $ origBody
(sub, tpl) = mkSubtsAndTpl newPats newBody
gen :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsDecl GhcPs
gen ps = uncurry reform . fromLambda . lambda ps
in [warn "Redundant lambda" o (gen pats origBody) [Replace Decl (toSS o) sub tpl]]
| let (newPats, newBody) = etaReduce pats origBody
, length newPats < length pats, pvars (drop (length newPats) pats) `disjoint` varss bind
= let (sub, tpl) = mkSubtsAndTpl newPats newBody
in [warn "Eta reduce" (reform pats origBody) (reform newPats newBody)
[Replace Decl (toSS $ reform pats origBody) sub tpl]
]
where reform :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsDecl GhcPs
reform ps b = L (combineSrcSpans loc1 loc2) $ ValD noExtField $
origBind
{fun_matches = MG noExtField (noLoc [noLoc $ Match noExtField ctxt ps $ GRHSs noExtField [noLoc $ GRHS noExtField [] b] $ noLoc $ EmptyLocalBinds noExtField]) Generated}
mkSubtsAndTpl newPats newBody = (sub, tpl)
where
(origPats, vars) = mkOrigPats (Just (rdrNameStr funName)) newPats
sub = ("body", toSS newBody) : zip vars (map toSS newPats)
tpl = unsafePrettyPrint (reform origPats varBody)
lambdaDecl _ = []
etaReduce :: [LPat GhcPs] -> LHsExpr GhcPs -> ([LPat GhcPs], LHsExpr GhcPs)
etaReduce (unsnoc -> Just (ps, view -> PVar_ p)) (L _ (HsApp _ x (view -> Var_ y)))
| p == y
, y `notElem` vars x
, not $ any isQuasiQuote $ universe x
= etaReduce ps x
etaReduce ps (L loc (OpApp _ x (isDol -> True) y)) = etaReduce ps (L loc (HsApp noExtField x y))
etaReduce ps x = (ps, x)
lambdaExp :: Maybe (LHsExpr GhcPs) -> LHsExpr GhcPs -> [Idea]
lambdaExp _ o@(L _ (HsPar _ (L _ (HsApp _ oper@(L _ (HsVar _ origf@(L _ (rdrNameOcc -> f)))) y))))
| isSymOcc f
, isAtom y
, allowLeftSection $ occNameString f
, not $ isTypeApp y
= [suggest "Use section" o to [r]]
where
to :: LHsExpr GhcPs
to = noLoc $ HsPar noExtField $ noLoc $ SectionL noExtField y oper
r = Replace Expr (toSS o) [("x", toSS y)] ("(x " ++ unsafePrettyPrint origf ++ ")")
lambdaExp _ o@(L _ (HsPar _ (view -> App2 (view -> Var_ "flip") origf@(view -> RdrName_ f) y)))
| allowRightSection (rdrNameStr f), not $ "(" `isPrefixOf` rdrNameStr f
= [suggest "Use section" o to [r]]
where
to :: LHsExpr GhcPs
to = noLoc $ HsPar noExtField $ noLoc $ SectionR noExtField origf y
op = if isSymbolRdrName (unLoc f)
then unsafePrettyPrint f
else "`" ++ unsafePrettyPrint f ++ "`"
var = if rdrNameStr f == "x" then "y" else "x"
r = Replace Expr (toSS o) [(var, toSS y)] ("(" ++ op ++ " " ++ var ++ ")")
lambdaExp p o@(L _ HsLam{})
| not $ any isOpApp p
, (res, refact) <- niceLambdaR [] o
, not $ isLambda res
, not $ any isQuasiQuote $ universe res
, not $ "runST" `Set.member` Set.map occNameString (freeVars o)
, let name = "Avoid lambda" ++ (if countRightSections res > countRightSections o then " using `infix`" else "")
, let from = case p of
Just p@(L _ (HsPar _ (L _ HsLam{})))
| L _ HsPar{} <- res -> p
| L _ (HsVar _ (L _ name)) <- res, not (isSymbolRdrName name) -> p
_ -> o
= [(if isVar res then warn else suggest) name from res (refact $ toSS from)]
where
countRightSections :: LHsExpr GhcPs -> Int
countRightSections x = length [() | L _ (SectionR _ (view -> Var_ _) _) <- universe x]
lambdaExp p o@(SimpleLambda origPats origBody)
| isLambda (fromParen origBody)
, null (universeBi origPats :: [HsExpr GhcPs])
, maybe True (not . isLambda) p =
[suggest "Collapse lambdas" o (lambda pats body) [Replace Expr (toSS o) subts template]]
where
(pats, body) = fromLambda o
(oPats, vars) = mkOrigPats Nothing pats
subts = ("body", toSS body) : zip vars (map toSS pats)
template = unsafePrettyPrint (lambda oPats varBody)
lambdaExp _ o@(SimpleLambda [view -> PVar_ x] (L _ expr)) =
case expr of
ExplicitTuple _ args boxity
| ([_x], ys) <- partition ((==Just x) . tupArgVar) args
, Set.notMember x $ Set.map occNameString $ freeVars ys
-> [(suggestN "Use tuple-section" o $ noLoc $ ExplicitTuple noExtField (map removeX args) boxity)
{ideaNote = [RequiresExtension "TupleSections"]}]
HsCase _ (view -> Var_ x') matchGroup
| x == x'
, Set.notMember x $ Set.map occNameString $ free $ allVars matchGroup
-> case matchGroup of
oldMG@(MG _ (L _ [L _ oldmatch]) _) ->
[suggestN "Use lambda" o $ noLoc $ HsLam noExtField oldMG
{ mg_alts = noLoc
[noLoc oldmatch
{ m_pats = map mkParPat $ m_pats oldmatch
, m_ctxt = LambdaExpr
}
] }
]
MG _ (L _ xs) _ ->
[(suggestN "Use lambda-case" o $ noLoc $ HsLamCase noExtField matchGroup)
{ideaNote=[RequiresExtension "LambdaCase"]}]
_ -> []
_ -> []
where
removeX :: LHsTupArg GhcPs -> LHsTupArg GhcPs
removeX arg@(L _ (Present _ (view -> Var_ x')))
| x == x' = noLoc $ Missing noExtField
removeX y = y
tupArgVar :: LHsTupArg GhcPs -> Maybe String
tupArgVar (L _ (Present _ (view -> Var_ x))) = Just x
tupArgVar _ = Nothing
lambdaExp _ _ = []
varBody :: LHsExpr GhcPs
varBody = strToVar "body"
fromLambda :: LHsExpr GhcPs -> ([LPat GhcPs], LHsExpr GhcPs)
fromLambda (SimpleLambda ps1 (fromLambda . fromParen -> (ps2,x))) = (transformBi (f $ pvars ps2) ps1 ++ ps2, x)
where f :: [String] -> Pat GhcPs -> Pat GhcPs
f bad (VarPat _ (rdrNameStr -> x))
| x `elem` bad = WildPat noExtField
f bad x = x
fromLambda x = ([], x)
mkOrigPats :: Maybe String -> [LPat GhcPs] -> ([LPat GhcPs], [String])
mkOrigPats funName pats = (zipWith munge vars pats', vars)
where
(Set.unions -> used, pats') = unzip (map f pats)
vars = filter (\s -> s `Set.notMember` used && Just s /= funName) substVars
f :: LPat GhcPs -> (Set String, (Bool, LPat GhcPs))
f p
| any isWildPat (universe p) =
let used = Set.fromList [rdrNameStr name | (L _ (VarPat _ name)) <- universe p]
in (used, (True, p))
| otherwise = (mempty, (False, p))
isWildPat :: LPat GhcPs -> Bool
isWildPat = \case (L _ (WildPat _)) -> True; _ -> False
munge :: String -> (Bool, LPat GhcPs) -> LPat GhcPs
munge _ (True, p) = p
munge ident (False, L ploc _) = L ploc (VarPat noExtField (L ploc $ mkRdrUnqual $ mkVarOcc ident))