{-# LANGUAGE PatternGuards, ViewPatterns, RecordWildCards, FlexibleContexts, ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-unused-top-binds #-}
module Hint.Match(readMatch,readMatch') where
import Control.Applicative
import Data.List.Extra
import Data.Maybe
import Config.Type
import Hint.Type
import Control.Monad
import Data.Tuple.Extra
import HSE.Unify
import Util
import Timing
import qualified Data.Set as Set
import Prelude
import qualified Refact.Types as R
import qualified HsSyn as GHC
import qualified SrcLoc as GHC
import qualified BasicTypes as GHC
import RdrName
import OccName
import Data.Data
import GHC.Util
fmapAn :: Exp b -> Exp SrcSpanInfo
fmapAn = fmap (const an)
readMatch :: [HintRule] -> DeclHint
readMatch settings = findIdeas (concatMap readRule settings)
readMatch' :: [HintRule] -> Scope' -> ModuleEx -> GHC.LHsDecl GHC.GhcPs -> [Idea]
readMatch' settings = findIdeas' (concatMap readRule' settings)
readRule :: HintRule -> [HintRule]
readRule m@HintRule{hintRuleLHS=(fmapAn -> hintRuleLHS), hintRuleRHS=(fmapAn -> hintRuleRHS), hintRuleSide=(fmap fmapAn -> hintRuleSide)} =
(:) m{hintRuleLHS=hintRuleLHS,hintRuleSide=hintRuleSide,hintRuleRHS=hintRuleRHS} $ do
(l,v1) <- dotVersion hintRuleLHS
(r,v2) <- dotVersion hintRuleRHS
guard $ v1 == v2 && l /= [] && (length l > 1 || length r > 1) && Set.notMember v1 (freeVars $ maybeToList hintRuleSide ++ l ++ r)
if r /= [] then
[m{hintRuleLHS=dotApps l, hintRuleRHS=dotApps r, hintRuleSide=hintRuleSide}
,m{hintRuleLHS=dotApps (l++[toNamed v1]), hintRuleRHS=dotApps (r++[toNamed v1]), hintRuleSide=hintRuleSide}]
else if length l > 1 then
[m{hintRuleLHS=dotApps l, hintRuleRHS=toNamed "id", hintRuleSide=hintRuleSide}
,m{hintRuleLHS=dotApps (l++[toNamed v1]), hintRuleRHS=toNamed v1, hintRuleSide=hintRuleSide}]
else []
readRule' :: HintRule -> [HintRule]
readRule' m@HintRule{ hintRuleGhcLHS=(stripLocs' . unwrap -> hintRuleGhcLHS)
, hintRuleGhcRHS=(stripLocs' . unwrap -> hintRuleGhcRHS)
, hintRuleGhcSide=((stripLocs' . unwrap <$>) -> hintRuleGhcSide)
} =
(:) m{ hintRuleGhcLHS=wrap hintRuleGhcLHS
, hintRuleGhcRHS=wrap hintRuleGhcRHS
, hintRuleGhcSide=wrap <$> hintRuleGhcSide } $ do
(l, v1) <- dotVersion' hintRuleGhcLHS
(r, v2) <- dotVersion' hintRuleGhcRHS
guard $ v1 == v2 && not (null l) && (length l > 1 || length r > 1) && Set.notMember v1 (Set.map occNameString (freeVars' $ maybeToList hintRuleGhcSide ++ l ++ r))
if not (null r) then
[ m{ hintRuleGhcLHS=wrap (dotApps' l), hintRuleGhcRHS=wrap (dotApps' r), hintRuleGhcSide=wrap <$> hintRuleGhcSide }
, m{ hintRuleGhcLHS=wrap (dotApps' (l ++ [strToVar' v1])), hintRuleGhcRHS=wrap (dotApps' (r ++ [strToVar' v1])), hintRuleGhcSide=wrap <$> hintRuleGhcSide } ]
else if length l > 1 then
[ m{ hintRuleGhcLHS=wrap (dotApps' l), hintRuleGhcRHS=wrap (strToVar' "id"), hintRuleGhcSide=wrap <$> hintRuleGhcSide }
, m{ hintRuleGhcLHS=wrap (dotApps' (l++[strToVar' v1])), hintRuleGhcRHS=wrap (strToVar' v1), hintRuleGhcSide=wrap <$> hintRuleGhcSide}]
else []
dotVersion :: Exp_ -> [([Exp_], String)]
dotVersion (view -> Var_ v) | isUnifyVar v = [([], v)]
dotVersion (App l ls rs) = first (ls :) <$> dotVersion (fromParen rs)
dotVersion (InfixApp l x op y) = (first (LeftSection l x op :) <$> dotVersion y) ++
(first (RightSection l op y:) <$> dotVersion x)
dotVersion _ = []
dotVersion' :: GHC.LHsExpr GHC.GhcPs -> [([GHC.LHsExpr GHC.GhcPs], String)]
dotVersion' (view' -> Var_' v) | isUnifyVar v = [([], v)]
dotVersion' (GHC.LL _ (GHC.HsApp _ ls rs)) = first (ls :) <$> dotVersion' (fromParen' rs)
dotVersion' (GHC.LL l (GHC.OpApp _ x op y)) =(first (GHC.cL l (GHC.SectionL GHC.noExt x op) :) <$> dotVersion' y) ++
(first (GHC.cL l (GHC.SectionR GHC.noExt op y) :) <$> dotVersion' x)
dotVersion' _ = []
findIdeas :: [HintRule] -> Scope -> ModuleEx -> Decl_ -> [Idea]
findIdeas matches s _ decl = timed "Hint" "Match apply" $ forceList
[ (idea (hintRuleSeverity m) (hintRuleName m) x y [r]){ideaNote=notes}
| decl <- findDecls decl
, (parent,x) <- universeParentExp decl
, m <- matches, Just (y,notes, subst) <- [matchIdea s decl m parent x]
, let r = R.Replace R.Expr (toSS x) subst (prettyPrint $ hintRuleRHS m) ]
findIdeas' :: [HintRule] -> Scope' -> ModuleEx -> GHC.LHsDecl GHC.GhcPs -> [Idea]
findIdeas' matches s _ decl = timed "Hint" "Match apply" $ forceList
[ (idea' (hintRuleSeverity m) (hintRuleName m) x y [r]){ideaNote=notes}
| decl <- findDecls' decl
, (parent,x) <- universeParentExp' decl
, m <- matches, Just (y, notes, subst) <- [matchIdea' s decl m parent x]
, let r = R.Replace R.Expr (toSS' x) subst (unsafePrettyPrint $ unwrap (hintRuleGhcRHS m))
]
findDecls :: Decl_ -> [Decl_]
findDecls x@InstDecl{} = children x
findDecls RulePragmaDecl{} = []
findDecls x = [x]
findDecls' :: GHC.LHsDecl GHC.GhcPs -> [GHC.LHsDecl GHC.GhcPs]
findDecls' x@(GHC.LL _ GHC.InstD{}) = children x
findDecls' (GHC.LL _ GHC.RuleD{}) = []
findDecls' x = [x]
matchIdea :: Scope -> Decl_ -> HintRule -> Maybe (Int, Exp_) -> Exp_ -> Maybe (Exp_, [Note], [(String, R.SrcSpan)])
matchIdea s decl HintRule{..} parent x = do
let nm a b = scopeMatch (hintRuleScope,a) (s,b)
u <- unifyExp nm True hintRuleLHS x
u <- validSubst (=~=) u
let e = substitute u hintRuleRHS
res = addBracket parent $ performSpecial $ substitute u $ unqualify hintRuleScope s hintRuleRHS
guard $ (freeVars e Set.\\ Set.filter (not . isUnifyVar) (freeVars hintRuleRHS))
`Set.isSubsetOf` freeVars x
guard $ not (any isLambda $ universe hintRuleLHS) || not (any isQuasiQuote $ universe x)
guard $ checkSide hintRuleSide $ ("original",x) : ("result",res) : fromSubst u
guard $ checkDefine decl parent res
return (res, hintRuleNotes, [(s, toSS pos) | (s, pos) <- fromSubst u, ann pos /= an])
matchIdea' :: Scope'
-> GHC.LHsDecl GHC.GhcPs
-> HintRule
-> Maybe (Int, GHC.LHsExpr GHC.GhcPs)
-> GHC.LHsExpr GHC.GhcPs
-> Maybe (GHC.LHsExpr GHC.GhcPs, [Note], [(String, R.SrcSpan)])
matchIdea' sb decl HintRule{..} parent x = do
let lhs = unwrap hintRuleGhcLHS
rhs = unwrap hintRuleGhcRHS
sa = unwrap hintRuleGhcScope
nm a b = scopeMatch' (sa, a) (sb, b)
u <- unifyExp' nm True lhs x
u <- validSubst' eqNoLoc' u
let e = substitute' u rhs
res = addBracket' parent $ performSpecial' $ substitute' u $ unqualify' sa sb rhs
guard $ (freeVars' e Set.\\ Set.filter (not . isUnifyVar . occNameString) (freeVars' rhs)) `Set.isSubsetOf` freeVars' x
guard $ not (any isLambda' $ universe lhs) || not (any isQuasiQuote' $ universe x)
guard $ checkSide' (unwrap <$> hintRuleGhcSide) $ ("original", x) : ("result", res) : fromSubst' u
guard $ checkDefine' decl parent res
return (res, hintRuleNotes, [(s, toSS' pos) | (s, pos) <- fromSubst' u, GHC.getLoc pos /= GHC.noSrcSpan])
checkSide :: Maybe Exp_ -> [(String, Exp_)] -> Bool
checkSide x bind = maybe True bool x
where
bool :: Exp_ -> Bool
bool (InfixApp _ x op y)
| opExp op ~= "&&" = bool x && bool y
| opExp op ~= "||" = bool x || bool y
| opExp op ~= "==" = expr (fromParen1 x) =~= expr (fromParen1 y)
bool (App _ x y) | x ~= "not" = not $ bool y
bool (Paren _ x) = bool x
bool (App _ cond (sub -> y))
| 'i':'s':typ <- fromNamed cond = isType typ y
bool (App _ (App _ cond (sub -> x)) (sub -> y))
| cond ~= "notIn" = and [x `notElem` universe y | x <- list x, y <- list y]
| cond ~= "notEq" = x /=~= y
bool x | x ~= "noTypeCheck" = True
bool x | x ~= "noQuickCheck" = True
bool x = error $ "Hint.Match.checkSide, unknown side condition: " ++ prettyPrint x
expr :: Exp_ -> Exp_
expr (App _ (fromNamed -> "subst") x) = sub $ fromParen1 x
expr x = x
isType "Compare" x = True
isType "Atom" x = isAtom x
isType "WHNF" x = isWHNF x
isType "Wildcard" x = any isFieldWildcard $ universeS x
isType "Nat" (asInt -> Just x) | x >= 0 = True
isType "Pos" (asInt -> Just x) | x > 0 = True
isType "Neg" (asInt -> Just x) | x < 0 = True
isType "NegZero" (asInt -> Just x) | x <= 0 = True
isType ('L':'i':'t':typ@(_:_)) (Lit _ x) =
let top = head (words $ show x) in
typ == top
isType typ x =
let top = head (words $ show x) in
typ == top
asInt :: Exp_ -> Maybe Integer
asInt (Paren _ x) = asInt x
asInt (NegApp _ x) = negate <$> asInt x
asInt (Lit _ (Int _ x _)) = Just x
asInt _ = Nothing
list :: Exp_ -> [Exp_]
list (List _ xs) = xs
list x = [x]
sub :: Exp_ -> Exp_
sub = transform f
where f (view -> Var_ x) | Just y <- lookup x bind = y
f x = x
checkSide' :: Maybe (GHC.LHsExpr GHC.GhcPs) -> [(String, GHC.LHsExpr GHC.GhcPs)] -> Bool
checkSide' x bind = maybe True bool x
where
bool :: GHC.LHsExpr GHC.GhcPs -> Bool
bool (GHC.LL _ (GHC.OpApp _ x op y))
| varToStr' op == "&&" = bool x && bool y
| varToStr' op == "||" = bool x || bool y
| varToStr' op == "==" = expr (fromParen1' x) `eqNoLoc'` expr (fromParen1' y)
bool (GHC.LL _ (GHC.HsApp _ x y)) | varToStr' x == "not" = not $ bool y
bool (GHC.LL _ (GHC.HsPar _ x)) = bool x
bool (GHC.LL _ (GHC.HsApp _ cond (sub -> y)))
| 'i' : 's' : typ <- varToStr' cond = isType typ y
bool (GHC.LL _ (GHC.HsApp _ (GHC.LL _ (GHC.HsApp _ cond (sub -> x))) (sub -> y)))
| varToStr' cond == "notIn" = and [wrap (stripLocs' x) `notElem` map (wrap . stripLocs') (universe y) | x <- list x, y <- list y]
| varToStr' cond == "notEq" = not (x `eqNoLoc'` y)
bool x | varToStr' x == "noTypeCheck" = True
bool x | varToStr' x == "noQuickCheck" = True
bool x = error $ "Hint.Match.checkSide', unknown side condition: " ++ unsafePrettyPrint x
expr :: GHC.LHsExpr GHC.GhcPs -> GHC.LHsExpr GHC.GhcPs
expr (GHC.LL _ (GHC.HsApp _ (varToStr' -> "subst") x)) = sub $ fromParen1' x
expr x = x
isType "Compare" x = True
isType "Atom" x = isAtom' x
isType "WHNF" x = isWHNF' x
isType "Wildcard" x = any isFieldPun' (universeBi x) || any hasFieldsDotDot' (universeBi x)
isType "Nat" (asInt -> Just x) | x >= 0 = True
isType "Pos" (asInt -> Just x) | x > 0 = True
isType "Neg" (asInt -> Just x) | x < 0 = True
isType "NegZero" (asInt -> Just x) | x <= 0 = True
isType "LitInt" (GHC.LL _ (GHC.HsLit _ GHC.HsInt{})) = True
isType "LitInt" (GHC.LL _ (GHC.HsOverLit _ (GHC.OverLit _ GHC.HsIntegral{} _))) = True
isType "Var" (GHC.LL _ GHC.HsVar{}) = True
isType "App" (GHC.LL _ GHC.HsApp{}) = True
isType "InfixApp" (GHC.LL _ x@GHC.OpApp{}) = True
isType "Paren" (GHC.LL _ x@GHC.HsPar{}) = True
isType "Tuple" (GHC.LL _ GHC.ExplicitTuple{}) = True
isType typ (GHC.LL _ x) =
let top = showConstr (toConstr x) in
typ == top
isType _ _ = False
asInt :: GHC.LHsExpr GHC.GhcPs -> Maybe Integer
asInt (GHC.LL _ (GHC.HsPar _ x)) = asInt x
asInt (GHC.LL _ (GHC.NegApp _ x _)) = negate <$> asInt x
asInt (GHC.LL _ (GHC.HsLit _ (GHC.HsInt _ (GHC.IL _ neg x)) )) = Just $ if neg then -x else x
asInt (GHC.LL _ (GHC.HsOverLit _ (GHC.OverLit _ (GHC.HsIntegral (GHC.IL _ neg x)) _))) = Just $ if neg then -x else x
asInt _ = Nothing
list :: GHC.LHsExpr GHC.GhcPs -> [GHC.LHsExpr GHC.GhcPs]
list (GHC.LL _ (GHC.ExplicitList _ _ xs)) = xs
list x = [x]
sub :: GHC.LHsExpr GHC.GhcPs -> GHC.LHsExpr GHC.GhcPs
sub = transform f
where f (view' -> Var_' x) | Just y <- lookup x bind = y
f x = x
checkDefine :: Decl_ -> Maybe (Int, Exp_) -> Exp_ -> Bool
checkDefine x Nothing y = fromNamed x /= fromNamed (transformBi unqual $ head $ fromApps y)
checkDefine _ _ _ = True
checkDefine' :: GHC.LHsDecl GHC.GhcPs -> Maybe (Int, GHC.LHsExpr GHC.GhcPs) -> GHC.LHsExpr GHC.GhcPs -> Bool
checkDefine' x Nothing y = declName x /= Just (varToStr' (transformBi unqual' $ head $ fromApps' y))
checkDefine' _ _ _ = True
performSpecial :: Exp_ -> Exp_
performSpecial = transform fNoParen . fEval
where
fEval (App _ e x) | e ~= "_eval_" = reduce x
fEval x = x
fNoParen (App _ e x) | e ~= "_noParen_" = fromParen x
fNoParen x = x
performSpecial' :: GHC.LHsExpr GHC.GhcPs -> GHC.LHsExpr GHC.GhcPs
performSpecial' = transform fNoParen . fEval
where
fEval, fNoParen :: GHC.LHsExpr GHC.GhcPs -> GHC.LHsExpr GHC.GhcPs
fEval (GHC.LL _ (GHC.HsApp _ e x)) | varToStr' e == "_eval_" = reduce' x
fEval x = x
fNoParen (GHC.LL _ (GHC.HsApp _ e x)) | varToStr' e == "_noParen_" = fromParen' x
fNoParen x = x
unqualify :: Scope -> Scope -> Exp_ -> Exp_
unqualify from to = transformBi f
where
f x@(UnQual _ (Ident _ s)) | isUnifyVar s = x
f x = scopeMove (from,x) to
unqualify' :: Scope' -> Scope' -> GHC.LHsExpr GHC.GhcPs -> GHC.LHsExpr GHC.GhcPs
unqualify' from to = transformBi f
where
f :: GHC.Located RdrName -> GHC.Located RdrName
f x@(GHC.L _ (Unqual s)) | isUnifyVar (occNameString s) = x
f x = scopeMove' (from, x) to
addBracket :: Maybe (Int,Exp_) -> Exp_ -> Exp_
addBracket (Just (i,p)) c | needBracketOld i p c = Paren an c
addBracket _ x = x
addBracket' :: Maybe (Int, GHC.LHsExpr GHC.GhcPs) -> GHC.LHsExpr GHC.GhcPs -> GHC.LHsExpr GHC.GhcPs
addBracket' (Just (i, p)) c | needBracketOld' i p c = GHC.noLoc $ GHC.HsPar GHC.noExt c
addBracket' _ x = x