diff -Nru hlint-2.2.11/CHANGES.txt hlint-3.1.6/CHANGES.txt --- hlint-2.2.11/CHANGES.txt 2020-02-09 21:29:46.000000000 +0000 +++ hlint-3.1.6/CHANGES.txt 2020-06-24 21:45:14.000000000 +0000 @@ -1,5 +1,120 @@ Changelog for HLint (* = breaking change) +3.1.6, released 2020-06-24 + #1062, make sure matching inserts brackets if required + #1058, weaken the self-definition check to match more things + #1060, suggest [] ++ x and [] ++ x to x +3.1.5, released 2020-06-19 + #1049, suggest or/and from True `elem` xs and False `notElem` xs + #1055, avoid incorrect hints with nested (.)'s + #1054, make isLitString work again + #1038, make -XNoTemplateHaskell imply -XNoTemplateHaskellQuotes + #970, require an arg to suggest fromMaybe True ==> (Just True ==) + #1047, suggest pushing take outside zip + #1041, fix language/pragma ordering in refactor + #1040, fix refactoring for "Avoid lambda" + #1042, fix redundant lambda refactoring + #1039, don't suggest move map inside list comp repeatedly + #1035, fix refactoring for "Use fewer imports" in some cases + #1036, disable refactoring for Use camelCase + #766, match quasi quotes properly in rules + Ignore [Char] to String hints by default + #1034, remove suggestions to use heirarchical module names + #1032, fix refactoring for "Use :" + #1028, add hints around sequenceA/traverse + #1027, pass enabled and disabled extensions to apply-refact + #1024, make the redundant bracket hints cover just the bracket + #1024, make redundant $ display more context on the command line + Suggest removing OverloadedLabels if there are no labels + #367, suggest removing OverloadedLists if there are no lists + #1023, speed up checking on large files (up to 12%) +3.1.4, released 2020-05-31 + #1018, stop --cross being quadratic + #1019, more rules suggesting even/odd +3.1.3, released 2020-05-25 + #1016, check scopes of restricted functions +3.1.2, released 2020-05-24 + #1014, don't error on empty do blocks + #1008, make redundant do ignored by default + #1012, add CodeWorld hints around pictures + #1003, enable refactoring for (v1 . v2) <$> v3 + #1002, warn on unused NumericUnderscores +3.1.1, released 2020-05-13 + #993, deal with infix declarations in the module they occur + #993, make createModuleEx use the default HLint fixities + #995, add unpackSrcSpan to the API +3.1, released 2020-05-07 + #979, suggest removing flip only for simple final variables + #978, do is not redundant with non-decreasing indentation + #969, wrong redundant bracket suggestion with BlockArguments + #970, detect redundant sections, (a +) b ==> a + b +* #974, split ParseFlags.extensions into enabled/disabled + #971, add support for -XNoFoo command line flags + #976, run refactor even if no hints + #971, add support for NoFoo language pragmas +3.0.4, released 2020-05-03 + #968, fail on all parse errors + #967, enable TypeApplications by default +3.0.3, released 2020-05-03 + #965, fix incorrect avoid lambda suggestion +3.0.2, released 2020-05-03 + #963, don't generate use-section hints for tuples + #745, fix up free variables for A{x}, fixes list comp hints +3.0.1, released 2020-05-02 + #961, don't crash on non-extension LANGUAGE pragmas, e.g. Safe +3.0, released 2020-05-02 + Be more permissive with names'with'quotes'in + #953, fix incorrect suggestions with free variables and \case + #955, make --find generate fixity: not infix: + #952, improve refactorings with qualified imports + #945, suggest Map.fromList [] ==> Map.empty + #949, warn about redundant fmaps with binds + #950, reduce the span of "Redundant $" to only cover the "$" + #944, reduce the span of "Use let" to only cover the "let" line + #669, don't suggest replacing reverse . sort (it's quite fast) + #939, reduce the span of "Redundant where" to only cover the "where" + Remove support for GHC 8.4 + Remove support for _eval, + #926, fix refactoring when the hint contains _noParen_ + #933, improve the output for Redundant do hints +* Merge ParseMode into ParseFlags +* Rename Language.Haskell.HLint3,HLint4 to Language.Haskell.HLint +* Delete the old Language.Haskell.HLint + #881, add a monomorphic group of hints + #837, don't suggest redundant do if its being used for brackets + #923, don't suggest eta reducing infix definitions + #931, disable StaticPointers extension by default + #924, remove dependency on haskell-src-exts + #922, reduce the span of "Redundant do" to only cover the "do" + #919, more specific names for foldMap fusion rules + #918, warn on unused TypeOperators + #916, warn on unused InstanceSigs + Improve parse error context messages +* Most parse errors are fixed + #881, disable hints about maybe that are sometimes wrong + #909, be more careful about redundant bracket warnings + #905, match hints even if there is composition to the left + #904, suggest map/fromMaybe[] becomes maybe [] map +* Remove the hse command line argument, to parse a file with HSE + #901, warn on unused MultiWayIf + Don't raise a parse error if haskell-src-exts can't parse code + #899, warn on unused PatternSynonyms + #898, don't suggest removing NamedFieldPuns with record updates +* Make any --hint flag disable implicit .hlint.yaml search +* Delete the --with flag +* Haskell hint definitions are no longer supported (use YAML) +* Report hints with src-span information, e.g. file:1:1-10 +* Delete resolveHints (it was the identity) +* Change to GHC types in the API + Add --with-group=future to add return ==> pure hint + #888, suggest foldr from (.) to ($) in some cases + #884, add more >=> operator hints + #875, fix the extension implication information + Add --with-group=extra to give extra library hints + #873, add more Applicative hints + #872, fix refactoring in hints to use lists + #871, warn when fmapping the result of gets or asks + #869, improve hints for maybe/fromMaybe on Bool 2.2.11, released 2020-02-09 #868, fix some brackets in refactoring suggestions #865, suggest biList if generalise-for-conciseness is turned on diff -Nru hlint-2.2.11/data/HLint_QuickCheck.hs hlint-3.1.6/data/HLint_QuickCheck.hs --- hlint-2.2.11/data/HLint_QuickCheck.hs 2015-01-12 21:18:19.000000000 +0000 +++ hlint-3.1.6/data/HLint_QuickCheck.hs 2020-03-28 20:02:39.000000000 +0000 @@ -102,7 +102,6 @@ Right v -> Just v _noParen_ = id -_eval_ = id withMain :: IO () -> IO () withMain act = do diff -Nru hlint-2.2.11/data/HLint_TypeCheck.hs hlint-3.1.6/data/HLint_TypeCheck.hs --- hlint-2.2.11/data/HLint_TypeCheck.hs 2015-01-12 21:18:19.000000000 +0000 +++ hlint-3.1.6/data/HLint_TypeCheck.hs 2020-03-28 20:02:26.000000000 +0000 @@ -6,7 +6,6 @@ (==>) = undefined _noParen_ = id -_eval_ = id --------------------------------------------------------------------- diff -Nru hlint-2.2.11/data/hlint.yaml hlint-3.1.6/data/hlint.yaml --- hlint-2.2.11/data/hlint.yaml 2020-02-09 21:16:43.000000000 +0000 +++ hlint-3.1.6/data/hlint.yaml 2020-06-24 20:56:26.000000000 +0000 @@ -50,6 +50,11 @@ - import Data.Attoparsec.Text - import Data.Attoparsec.ByteString +- package: + name: codeworld-api + modules: + - import CodeWorld + - group: name: default enabled: true @@ -102,7 +107,8 @@ - warn: {lhs: sortBy (flip (comparing f)), rhs: sortOn (Down . f)} - warn: {lhs: sortBy (comparing f), rhs: sortOn f, side: notEq f fst && notEq f snd} - warn: {lhs: reverse (sortOn f x), rhs: sortOn (Data.Ord.Down . f) x, name: Avoid reverse, note: Stabilizes sort order} - - warn: {lhs: reverse (sort x), rhs: sortOn Data.Ord.Down x, name: Avoid reverse, note: Stabilizes sort order} + # This suggestion likely costs performance, see https://github.com/ndmitchell/hlint/issues/669#issuecomment-607154496 + # - warn: {lhs: reverse (sort x), rhs: sortOn Data.Ord.Down x, name: Avoid reverse, note: Stabilizes sort order} - hint: {lhs: flip (g `on` h), rhs: flip g `on` h, name: Move flip} - hint: {lhs: (f `on` g) `on` h, rhs: f `on` (g . h), name: Fuse on/on} @@ -142,6 +148,7 @@ - warn: {lhs: foldl f (head x) (tail x), rhs: foldl1 f x} - warn: {lhs: foldr f (last x) (init x), rhs: foldr1 f x} - warn: {lhs: "foldr (\\c a -> x : a) []", rhs: "map (\\c -> x)"} + - warn: {lhs: foldr (.) id l z, rhs: foldr ($) z l} - warn: {lhs: span (not . p), rhs: break p} - warn: {lhs: break (not . p), rhs: span p} - warn: {lhs: "(takeWhile p x, dropWhile p x)", rhs: span p x, note: DecreasesLaziness} @@ -175,6 +182,7 @@ - hint: {lhs: 0 /= length x, rhs: not (null x), note: IncreasesLaziness, name: Use null} - hint: {lhs: "\\x -> [x]", rhs: "(:[])", name: "Use :"} - hint: {lhs: map f (zip x y), rhs: zipWith (curry f) x y, side: not (isApp f)} + - hint: {lhs: "map f (fromMaybe [] x)", rhs: "maybe [] (map f) x"} - warn: {lhs: not (elem x y), rhs: notElem x y} - hint: {lhs: foldr f z (map g x), rhs: foldr (f . g) z x, name: Fuse foldr/map} - warn: {lhs: "x ++ concatMap (' ':) y", rhs: "unwords (x:y)"} @@ -196,6 +204,8 @@ - warn: {lhs: all (a /=), rhs: notElem a, note: ValidInstance Eq a} - warn: {lhs: elem True, rhs: or} - warn: {lhs: notElem False, rhs: and} + - warn: {lhs: True `elem` l, rhs: or l} + - warn: {lhs: False `notElem` l, rhs: and l} - warn: {lhs: findIndex ((==) a), rhs: elemIndex a} - warn: {lhs: findIndex (a ==), rhs: elemIndex a} - warn: {lhs: findIndex (== a), rhs: elemIndex a} @@ -219,6 +229,8 @@ - warn: {lhs: zipWith f (repeat x), rhs: map (f x)} - warn: {lhs: zipWith f y (repeat z), rhs: map (\x -> f x z) y} - warn: {lhs: listToMaybe (filter p x), rhs: find p x} + - warn: {lhs: zip (take n x) (take n y), rhs: take n (zip x y)} + - warn: {lhs: zip (take n x) (take m y), rhs: take (min n m) (zip x y), side: notEq n m, note: IncreasesLaziness, name: Redundant take} # MONOIDS @@ -233,14 +245,13 @@ - warn: {lhs: sequenceA (map f x), rhs: traverse f x} - warn: {lhs: sequenceA (fmap f x), rhs: traverse f x} - - warn: {lhs: sequence (fmap f x), rhs: traverse f x} - warn: {lhs: sequenceA_ (map f x), rhs: traverse_ f x} - warn: {lhs: sequenceA_ (fmap f x), rhs: traverse_ f x} - warn: {lhs: foldMap id, rhs: fold} - warn: {lhs: fold (fmap f x), rhs: foldMap f x} - warn: {lhs: fold (map f x), rhs: foldMap f x} - - warn: {lhs: foldMap f (fmap g x), rhs: foldMap (f . g) x} - - warn: {lhs: foldMap f (map g x), rhs: foldMap (f . g) x} + - warn: {lhs: foldMap f (fmap g x), rhs: foldMap (f . g) x, name: Fuse foldMap/fmap} + - warn: {lhs: foldMap f (map g x), rhs: foldMap (f . g) x, name: Fuse foldMap/map} # BY @@ -297,7 +308,7 @@ - warn: {lhs: (Data.Function.& f), rhs: f, name: Redundant Data.Function.&} - hint: {lhs: \x -> y, rhs: const y, side: isAtom y && not (isWildcard y)} # If any isWildcard recursively then x may be used but not mentioned explicitly - - warn: {lhs: flip f x y, rhs: f y x, side: isApp original} + - warn: {lhs: flip f x y, rhs: f y x, side: isApp original && isAtom y} - warn: {lhs: id x, rhs: x} - warn: {lhs: id . x, rhs: x, name: Redundant id} - warn: {lhs: x . id, rhs: x, name: Redundant id} @@ -344,8 +355,6 @@ - warn: {lhs: if x then False else y, rhs: not x && y, side: notEq y True, name: Redundant if} - warn: {lhs: if x then y else True, rhs: not x || y, side: notEq y False, name: Redundant if} - warn: {lhs: not (not x), rhs: x, name: Redundant not} - # warn "Too strict if": {lhs: if c then f x else f y, rhs: f (if c then x else y), note: IncreasesLaziness} - # also breaks types, see #87 # ARROW @@ -384,7 +393,7 @@ - warn: {lhs: f <$> g <$> x, rhs: f . g <$> x, name: Functor law} - warn: {lhs: fmap id, rhs: id, name: Functor law} - warn: {lhs: id <$> x, rhs: x, name: Functor law} - - hint: {lhs: fmap f $ x, rhs: f Control.Applicative.<$> x, side: isApp x || isAtom x} + - hint: {lhs: fmap f $ x, rhs: f <$> x, side: isApp x || isAtom x} - hint: {lhs: \x -> a <$> b x, rhs: fmap a . b} - hint: {lhs: x *> pure y, rhs: x Data.Functor.$> y} - hint: {lhs: x *> return y, rhs: x Data.Functor.$> y} @@ -414,6 +423,12 @@ - warn: {lhs: liftA, rhs: fmap} - hint: {lhs: m >>= return . f, rhs: f <$> m} - hint: {lhs: return . f =<< m, rhs: f <$> m} + - warn: {lhs: fmap f x >>= g, rhs: x >>= g . f} + - warn: {lhs: f <$> x >>= g, rhs: x >>= g . f} + - warn: {lhs: x Data.Functor.<&> f >>= g, rhs: x >>= g . f} + - warn: {lhs: g =<< fmap f x, rhs: g . f =<< x} + - warn: {lhs: g =<< f <$> x, rhs: g . f =<< x} + - warn: {lhs: g =<< (x Data.Functor.<&> f), rhs: g . f =<< x} - warn: {lhs: if x then y else return (), rhs: Control.Monad.when x $ _noParen_ y, side: not (isAtom y)} - warn: {lhs: if x then y else return (), rhs: Control.Monad.when x y, side: isAtom y} - warn: {lhs: if x then return () else y, rhs: Control.Monad.unless x $ _noParen_ y, side: isAtom y} @@ -443,8 +458,15 @@ - warn: {lhs: flip (=<<), rhs: (>>=)} - hint: {lhs: \x -> f x >>= g, rhs: f Control.Monad.>=> g} - hint: {lhs: \x -> f =<< g x, rhs: f Control.Monad.<=< g} + - hint: {lhs: (>>= f) . g, rhs: f Control.Monad.<=< g} + - hint: {lhs: (f =<<) . g, rhs: f Control.Monad.<=< g} - warn: {lhs: a >> forever a, rhs: forever a} - hint: {lhs: liftM2 id, rhs: ap} + - warn: {lhs: liftA2 f (return x), rhs: fmap (f x)} + - warn: {lhs: liftM2 f (pure x), rhs: fmap (f x)} + - warn: {lhs: liftM2 f (return x), rhs: fmap (f x)} + - warn: {lhs: fmap f (return x), rhs: return (f x)} + - warn: {lhs: f <$> return x, rhs: return (f x)} - warn: {lhs: mapM (uncurry f) (zip l m), rhs: zipWithM f l m} - warn: {lhs: mapM_ (void . f), rhs: mapM_ f} - warn: {lhs: forM_ x (void . f), rhs: forM_ x f} @@ -466,6 +488,10 @@ - warn: {lhs: sequence_ (zipWith f x y), rhs: Control.Monad.zipWithM_ f x y} - warn: {lhs: sequence (replicate n x), rhs: Control.Monad.replicateM n x} - warn: {lhs: sequence_ (replicate n x), rhs: Control.Monad.replicateM_ n x} + - warn: {lhs: sequenceA (zipWith f x y), rhs: Control.Monad.zipWithM f x y} + - warn: {lhs: sequenceA_ (zipWith f x y), rhs: Control.Monad.zipWithM_ f x y} + - warn: {lhs: sequenceA (replicate n x), rhs: Control.Monad.replicateM n x} + - warn: {lhs: sequenceA_ (replicate n x), rhs: Control.Monad.replicateM_ n x} - warn: {lhs: mapM f (replicate n x), rhs: Control.Monad.replicateM n (f x)} - warn: {lhs: mapM_ f (replicate n x), rhs: Control.Monad.replicateM_ n (f x)} - warn: {lhs: mapM f (map g x), rhs: mapM (f . g) x, name: Fuse mapM/map} @@ -484,11 +510,16 @@ - warn: {lhs: foldr (*>) (pure ()), rhs: sequenceA_} - warn: {lhs: foldr (<|>) empty, rhs: asum} - warn: {lhs: liftA2 (flip ($)), rhs: (<**>)} + - warn: {lhs: liftA2 f (pure x), rhs: fmap (f x)} + - warn: {lhs: fmap f (pure x), rhs: pure (f x)} + - warn: {lhs: f <$> pure x, rhs: pure (f x)} - warn: {lhs: Just <$> a <|> pure Nothing, rhs: optional a} - hint: {lhs: m >>= pure . f, rhs: f <$> m} - hint: {lhs: pure . f =<< m, rhs: f <$> m} - warn: {lhs: empty <|> x, rhs: x, name: "Alternative law, left identity"} - warn: {lhs: x <|> empty, rhs: x, name: "Alternative law, right identity"} + - warn: {lhs: traverse id, rhs: sequenceA} + - warn: {lhs: traverse_ id, rhs: sequenceA_} # LIST COMP @@ -518,8 +549,12 @@ - warn: {lhs: maybe Nothing Just, rhs: id, name: Redundant maybe} - warn: {lhs: maybe False (const True), rhs: Data.Maybe.isJust} - warn: {lhs: maybe True (const False), rhs: Data.Maybe.isNothing} - - warn: {lhs: maybe False (== x), rhs: (== Just x)} - - warn: {lhs: maybe True (/= x), rhs: (/= Just x)} + - warn: {lhs: maybe False (x ==), rhs: (Just x ==)} + - warn: {lhs: maybe True (x /=), rhs: (Just x /=)} + - warn: {lhs: maybe False (== x), rhs: (Just x ==), note: ValidInstance Eq x} + - warn: {lhs: maybe True (/= x), rhs: (Just x /=), note: ValidInstance Eq x} + - warn: {lhs: fromMaybe False x, rhs: Just True == x} # Eta expanded, see https://github.com/ndmitchell/hlint/issues/970#issuecomment-643645053 + - warn: {lhs: fromMaybe True x, rhs: Just False /= x} - warn: {lhs: not (isNothing x), rhs: isJust x} - warn: {lhs: not (isJust x), rhs: isNothing x} - warn: {lhs: "maybe [] (:[])", rhs: maybeToList} @@ -551,8 +586,6 @@ - hint: {lhs: case m of Nothing -> Nothing; Just x -> x, rhs: Control.Monad.join m} - hint: {lhs: maybe Nothing id, rhs: join} - hint: {lhs: maybe Nothing f x, rhs: f =<< x} - - hint: {lhs: maybe (f x) (f . g), rhs: f . maybe x g, note: IncreasesLaziness, name: Too strict maybe} - - hint: {lhs: maybe (f x) f y, rhs: f (Data.Maybe.fromMaybe x y), note: IncreasesLaziness, name: Too strict maybe} - warn: {lhs: maybe x f (fmap g y), rhs: maybe x (f . g) y, name: Redundant fmap} - warn: {lhs: isJust (fmap f x), rhs: isJust x} - warn: {lhs: isNothing (fmap f x), rhs: isNothing x} @@ -593,6 +626,10 @@ - hint: {lhs: 0 == rem n 2, rhs: even n} - hint: {lhs: rem n 2 /= 0, rhs: odd n} - hint: {lhs: 0 /= rem n 2, rhs: odd n} + - hint: {lhs: mod n 2 == 0, rhs: even n} + - hint: {lhs: 0 == mod n 2, rhs: even n} + - hint: {lhs: mod n 2 /= 0, rhs: odd n} + - hint: {lhs: 0 /= mod n 2, rhs: odd n} - hint: {lhs: not (even x), rhs: odd x} - hint: {lhs: not (odd x), rhs: even x} - hint: {lhs: x ** 0.5, rhs: sqrt x} @@ -652,8 +689,12 @@ - warn: {lhs: f <$> Control.Monad.State.get, rhs: gets f} - warn: {lhs: fmap f Control.Monad.State.get, rhs: gets f} + - warn: {lhs: f <$> Control.Monad.State.gets g, rhs: gets (f . g)} + - warn: {lhs: fmap f (Control.Monad.State.gets g), rhs: gets (f . g)} - warn: {lhs: f <$> Control.Monad.Reader.ask, rhs: asks f} - warn: {lhs: fmap f Control.Monad.Reader.ask, rhs: asks f} + - warn: {lhs: f <$> Control.Monad.Reader.asks g, rhs: asks (f . g)} + - warn: {lhs: fmap f (Control.Monad.Reader.asks g), rhs: asks (f . g)} - warn: {lhs: fst (runState m s), rhs: evalState m s} - warn: {lhs: snd (runState m s), rhs: execState m s} @@ -696,6 +737,8 @@ - warn: {lhs: const x y, rhs: x, name: Evaluate} - warn: {lhs: any (const False), rhs: const False, note: IncreasesLaziness, name: Evaluate} - warn: {lhs: all (const True), rhs: const True, note: IncreasesLaziness, name: Evaluate} + - warn: {lhs: "[] ++ x", rhs: x, name: Evaluate} + - warn: {lhs: "x ++ []", rhs: x, name: Evaluate} # FOLDABLE + TUPLES @@ -742,6 +785,12 @@ - warn: {lhs: null x , rhs: "False", side: isTuple x, name: Using null on tuple} - warn: {lhs: length x, rhs: "1" , side: isTuple x, name: Using length on tuple} + # MAP + + - warn: {lhs: "Data.Map.fromList []", rhs: Data.Map.empty} + - warn: {lhs: "Data.Map.Lazy.fromList []", rhs: Data.Map.Lazy.empty} + - warn: {lhs: "Data.Map.Strict.fromList []", rhs: Data.Map.Strict.empty} + - group: name: lens enabled: true @@ -811,8 +860,6 @@ - warn: {lhs: either (const False), rhs: any} - warn: {lhs: either (const True), rhs: all} - warn: {lhs: Data.Maybe.fromMaybe mempty, rhs: Data.Foldable.fold} - - warn: {lhs: Data.Maybe.fromMaybe False, rhs: or} - - warn: {lhs: Data.Maybe.fromMaybe True, rhs: and} - warn: {lhs: Data.Maybe.fromMaybe 0, rhs: sum} - warn: {lhs: Data.Maybe.fromMaybe 1, rhs: product} - warn: {lhs: Data.Maybe.fromMaybe empty, rhs: Data.Foldable.asum} @@ -831,6 +878,77 @@ - hint: {lhs: "[fst x, snd x]", rhs: Data.Bifoldable.biList x} - hint: {lhs: "\\(x, y) -> [x, y]", rhs: Data.Bifoldable.biList, note: IncreasesLaziness} +# hints that use the 'extra' library +- group: + name: extra + enabled: false + rules: + - warn: {lhs: fmap concat (forM a b), rhs: concatForM a b} + - warn: {lhs: concat <$> forM a b, rhs: concatForM a b} + - warn: {lhs: fmap concat (forM_ a b), rhs: concatForM_ a b} + - warn: {lhs: concat <$> forM_ a b, rhs: concatForM_ a b} + - warn: {lhs: "maybe (pure ()) b a", rhs: "whenJust a b"} + - warn: {lhs: "maybe (return ()) b a", rhs: "whenJust a b"} + - warn: {lhs: "maybeM (pure ()) b a", rhs: "whenJustM a b"} + - warn: {lhs: "maybeM (return ()) b a", rhs: "whenJustM a b"} + - warn: {lhs: "if a then Just <$> b else pure Nothing", rhs: "whenMaybe a b"} + - warn: {lhs: "maybe a b =<< c", rhs: "maybeM a b c"} + - warn: {lhs: "maybeM a pure x", rhs: "fromMaybeM a b"} + - warn: {lhs: "maybeM a return x", rhs: "fromMaybeM a b"} + - warn: {lhs: "either a b =<< c", rhs: "eitherM a b c"} + - warn: {lhs: "fold1M a b >> return ()", rhs: "fold1M_ a b"} + - warn: {lhs: "fold1M a b >> pure ()", rhs: "fold1M_ a b"} + - warn: {lhs: "flip concatMapM", rhs: "concatForM"} + - warn: {lhs: "liftM mconcat (mapM a b)", rhs: "mconcatMapM a b"} + - warn: {lhs: "ifM a b (return ())", rhs: "whenM a b"} + - warn: {lhs: "ifM a (return ()) b", rhs: "unlessM a b"} + - warn: {lhs: "ifM a (return True) b", rhs: "(||^) a b"} + - warn: {lhs: "ifM a b (return False)", rhs: "(&&^) a b"} + - warn: {lhs: "anyM id", rhs: "orM"} + - warn: {lhs: "allM id", rhs: "andM"} + - warn: {lhs: "either id id", rhs: "fromEither"} + - warn: {lhs: "either (const Nothing) Just", rhs: "eitherToMaybe"} + - warn: {lhs: "either (Left . a) Right", rhs: "mapLeft a"} + - warn: {lhs: "atomicModifyIORef a (\\ v -> (b v, ()))", rhs: "atomicModifyIORef_ a b"} + - warn: {lhs: "atomicModifyIORef' a (\\ v -> (b v, ()))", rhs: "atomicModifyIORef'_ a b"} + - warn: {lhs: "null (intersect a b)", rhs: "disjoint a b"} + - warn: {lhs: "[minBound .. maxBound]", rhs: "enumerate"} + - warn: {lhs: "zipWithFrom (,)", rhs: "zipFrom"} + - warn: {lhs: "zip [i..]", rhs: "zipFrom i"} + - warn: {lhs: "zipWith f [i..]", rhs: "zipWithFrom f i"} + - warn: {lhs: "dropWhile isSpace", rhs: "trimStart"} + - warn: {lhs: "dropWhileEnd isSpace", rhs: "trimEnd"} + - warn: {lhs: "trimEnd (trimStart a)", rhs: "trim a"} + - warn: {lhs: "map toLower", rhs: "lower"} + - warn: {lhs: "map toUpper", rhs: "upper"} + - warn: {lhs: "mergeBy compare", rhs: "merge"} + - warn: {lhs: "breakEnd (not . a)", rhs: "spanEnd a"} + - warn: {lhs: "spanEnd (not . a)", rhs: "breakEnd a"} + - warn: {lhs: "mconcat (map a b)", rhs: "mconcatMap a b"} + - warn: {lhs: "fromMaybe b (stripPrefix a b)", rhs: "dropPrefix a b"} + - warn: {lhs: "fromMaybe b (stripSuffix a b)", rhs: "dropSuffix a b"} + - warn: {lhs: "nubSortBy compare", rhs: "nubSort"} + - warn: {lhs: "nubSortBy (compare `on` a)", rhs: "nubSortOn a"} + - warn: {lhs: "nubOrdBy compare", rhs: "nubOrd"} + - warn: {lhs: "\\a -> (a, a)", rhs: "dupe"} + - warn: {lhs: "showFFloat (Just a) b \"\"", rhs: "showDP a b"} + - warn: {lhs: "readFileEncoding utf8", rhs: "readFileUTF8"} + - warn: {lhs: "withFile a ReadMode hGetContents'", rhs: "readFile' a"} + - warn: {lhs: "readFileEncoding' utf8", rhs: "readFileUTF8'"} + - warn: {lhs: "withBinaryFile a ReadMode hGetContents'", rhs: "readFileBinary' a"} + - warn: {lhs: "writeFileEncoding utf8", rhs: "writeFileUTF8"} + - warn: {lhs: "head $ x ++ [y]", rhs: "headDef y x"} + - warn: {lhs: "last $ x : y", rhs: "lastDef x y"} + - warn: {lhs: "drop 1", rhs: "drop1"} + - warn: {lhs: "dropEnd 1", rhs: "dropEnd1"} + +# hints that will be enabled in future +- group: + name: future + enabled: false + rules: + - warn: {lhs: return, rhs: pure} + - group: name: dollar enabled: false @@ -840,6 +958,35 @@ - warn: {lhs: a $ b $ c, rhs: a . b $ c} - group: + # These hints are same if all matched functions are monomorphic, or polymorphic, but don't have adhoc polymorphism + name: monomorphic + enabled: false + imports: + - package base + rules: + - warn: {lhs: if c then f x else f y, rhs: f (if c then x else y), note: IncreasesLaziness, name: Too strict if} + - hint: {lhs: maybe (f x) (f . g), rhs: f . maybe x g, note: IncreasesLaziness, name: Too strict maybe} + - hint: {lhs: maybe (f x) f y, rhs: f (Data.Maybe.fromMaybe x y), note: IncreasesLaziness, name: Too strict maybe} + +- group: + name: codeworld + enabled: false + imports: + - package base + - package codeworld-api + rules: + - warn: {lhs: "pictures [ p ]", rhs: p, name: Evaluate} + - warn: {lhs: "pictures [ p, q ]", rhs: p & q, name: Evaluate} + - hint: {lhs: foldl1 (&), rhs: pictures} + - hint: {lhs: foldr (&) blank, rhs: pictures} + - hint: {lhs: scaled x x, rhs: dilated x} + - hint: {lhs: scaledPoint x x, rhs: dilatedPoint x} + - warn: {lhs: "brighter (- a)", rhs: "duller a"} + - warn: {lhs: "lighter (- a)", rhs: "darker a"} + - warn: {lhs: "duller (- a)", rhs: "brighter a"} + - warn: {lhs: "darker (- a)", rhs: "lighter a"} + +- group: name: teaching enabled: false imports: @@ -849,6 +996,14 @@ - hint: {lhs: "[] /= x", rhs: not (null x), name: Use null} - hint: {lhs: "not (x || y)", rhs: "not x && not y", name: Apply De Morgan law} - hint: {lhs: "not (x && y)", rhs: "not x || not y", name: Apply De Morgan law} + - hint: {lhs: "[ f x | x <- l ]", rhs: map f l} + +- group: + # used for tests, enabled when testing this file + name: testing + enabled: false + rules: + - warn: {lhs: "[issue766| |]", rhs: "mempty", name: "Use mempty"} # # yes = concat . map f -- concatMap f @@ -902,6 +1057,8 @@ # no = foo $ \(a, b) -> (a, a + b) # yes = map (uncurry (+)) $ zip [1 .. 5] [6 .. 10] -- zipWith (curry (uncurry (+))) [1 .. 5] [6 .. 10] # yes = curry (uncurry (+)) -- (+) +# yes = fst foo .= snd foo -- uncurry (.=) foo +# yes = fst foo `_ba__'r''` snd foo -- uncurry _ba__'r'' foo # no = do iter <- textBufferGetTextIter tb ; textBufferSelectRange tb iter iter # no = flip f x $ \y -> y*y+y # no = \x -> f x (g x) @@ -927,6 +1084,7 @@ # yes = if p then s else return () -- Control.Monad.when p s # warn = a $$$$ b $$$$ c ==> a . b $$$$$ c # yes = when (not . null $ asdf) -- unless (null asdf) +# yes = (foo . bar . when) (not . null $ asdf) -- (foo . bar) (unless (null asdf)) # yes = id 1 -- 1 # yes = case concat (map f x) of [] -> [] -- concatMap f x # yes = [v | v <- xs] -- xs @@ -946,10 +1104,13 @@ # test = \ a -> f a >>= \ b -> return (a, b) # fooer input = catMaybes . map Just $ input -- mapMaybe Just # yes = mapMaybe id -- catMaybes +# foo = magic . isLeft $ fmap f x -- magic (isLeft x) +# foo = (bar . baz . magic . isRight) (fmap f x) -- (bar . baz . magic) (isRight x) # main = print $ map (\_->5) [2,3,5] -- const 5 # main = head $ drop n x -- x !! max 0 n # main = head $ drop (-3) x -- x # main = head $ drop 2 x -- x !! 2 +# main = foo . bar . baz . head $ drop 2 x -- (foo . bar . baz) (x !! 2) # main = drop 0 x -- x # main = take 0 x -- [] # main = take (-5) x -- [] @@ -963,7 +1124,7 @@ # pairs (x:xs) = map (x,) xs ++ pairs xs # {-# ANN foo "HLint: ignore" #-};foo = map f (map g x) -- @Ignore ??? # {-# HLINT ignore foo #-};foo = map f (map g x) -- @Ignore ??? -# yes = fmap lines $ abc 123 -- lines Control.Applicative.<$> abc 123 +# yes = fmap lines $ abc 123 -- lines <$> abc 123 # no = fmap lines $ abc $ def 123 # test = foo . not . not -- id # test = map (not . not) xs -- id @@ -1007,8 +1168,14 @@ # no = sequenceA (pure a) # {-# LANGUAGE QuasiQuotes #-}; no = f (\url -> [hamlet|foo @{url}|]) # yes = f ((,) x) -- (x,) +# yes = f ((,) (2 + 3)) -- (2 + 3,) # instance Class X where method = map f (map g x) -- map (f . g) x # instance Eq X where x == y = compare x y == EQ +# issue1055 = map f ((sort . map g) xs) +# issue1049 = True `elem` xs -- or xs +# issue1049 = elem True -- or +# issue1062 = bar (\(f, x) -> baz () () . f $ x) -- uncurry ((.) (baz () ())) +# issue1058 n = [] ++ issue1058 (n+1) -- issue1058 (n+1) # import Prelude \ # yes = flip mapM -- Control.Monad.forM @@ -1045,4 +1212,20 @@ # foo = typeOf (undefined :: Foo Int) -- typeRep (Proxy :: Proxy (Foo Int)) # foo = typeOf (undefined :: a) -- typeRep (Proxy :: Proxy a) # {-# RULES "Id-fmap-id" forall (x :: Id a). fmap id x = x #-} +# import Data.Map (fromList) \ +# fromList [] -- Data.Map.empty +# import Data.Map.Lazy (fromList) \ +# fromList [] -- Data.Map.Lazy.empty +# import Data.Map.Strict (fromList) \ +# fromList [] -- Data.Map.Strict.empty +# test953 = for [] $ \n -> bar n >>= \case {Just n -> pure (); Nothing -> baz n} +# f = map (flip (,) "a") "123" -- (,"a") +# f = map ((,) "a") "123" -- ("a",) +# test979 = flip Map.traverseWithKey blocks \k v -> lots_of_code_goes_here +# infixl 4 <*! \ +# test993 = f =<< g <$> x <*! y +# {-# LANGUAGE QuasiQuotes #-} \ +# test = [issue766| |] -- mempty +# {-# LANGUAGE QuasiQuotes #-} \ +# test = [issue766| x |] # diff -Nru hlint-2.2.11/debian/changelog hlint-3.1.6/debian/changelog --- hlint-2.2.11/debian/changelog 2020-08-13 18:27:08.000000000 +0000 +++ hlint-3.1.6/debian/changelog 2020-08-30 00:07:34.000000000 +0000 @@ -1,8 +1,14 @@ -hlint (2.2.11-1build1) groovy; urgency=medium +hlint (3.1.6-1build1) groovy; urgency=medium - * Rebuild against new GHC ABI. + * No-change rebuild for new GHC ABIs - -- Gianfranco Costamagna Thu, 13 Aug 2020 20:27:08 +0200 + -- Steve Langasek Sun, 30 Aug 2020 00:07:34 +0000 + +hlint (3.1.6-1) unstable; urgency=medium + + * New upstream release + + -- Ilias Tsitsimpis Fri, 21 Aug 2020 07:59:54 +0300 hlint (2.2.11-1) unstable; urgency=medium diff -Nru hlint-2.2.11/debian/control hlint-3.1.6/debian/control --- hlint-2.2.11/debian/control 2020-07-20 05:15:30.000000000 +0000 +++ hlint-3.1.6/debian/control 2020-08-30 00:07:34.000000000 +0000 @@ -1,5 +1,6 @@ Source: hlint -Maintainer: Debian Haskell Group +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Debian Haskell Group Uploaders: Joachim Breitner , Priority: optional @@ -21,20 +22,18 @@ libghc-cpphs-prof, libghc-data-default-dev (>= 0.3), libghc-data-default-prof, - libghc-extra-dev (>= 1.6.6), + libghc-extra-dev (>= 1.7.3), libghc-extra-prof, libghc-file-embed-dev, libghc-file-embed-prof, libghc-filepattern-dev (>= 0.1.1), libghc-filepattern-prof, - libghc-ghc-lib-parser-ex-dev (>= 8.8.5), - libghc-ghc-lib-parser-ex-dev (<< 8.8.6), + libghc-ghc-lib-parser-dev (>= 8.10), + libghc-ghc-lib-parser-dev (<< 8.11), + libghc-ghc-lib-parser-prof, + libghc-ghc-lib-parser-ex-dev (>= 8.10.0.14), + libghc-ghc-lib-parser-ex-dev (<< 8.10.1), libghc-ghc-lib-parser-ex-prof, - libghc-src-exts-dev (>= 1.21), - libghc-src-exts-dev (<< 1.24), - libghc-src-exts-prof, - libghc-src-exts-util-dev (>= 0.2.5), - libghc-src-exts-util-prof, libghc-hscolour-dev (>= 1.21), libghc-hscolour-prof, libghc-refact-dev (>= 0.3), @@ -58,9 +57,8 @@ libghc-extra-doc, libghc-file-embed-doc, libghc-filepattern-doc, + libghc-ghc-lib-parser-doc, libghc-ghc-lib-parser-ex-doc, - libghc-src-exts-doc, - libghc-src-exts-util-doc, libghc-hscolour-doc, libghc-refact-doc, libghc-src-exts-doc, diff -Nru hlint-2.2.11/hlint.cabal hlint-3.1.6/hlint.cabal --- hlint-2.2.11/hlint.cabal 2020-02-09 21:29:35.000000000 +0000 +++ hlint-3.1.6/hlint.cabal 2020-06-24 21:45:39.000000000 +0000 @@ -1,7 +1,7 @@ cabal-version: >= 1.18 build-type: Simple name: hlint -version: 2.2.11 +version: 3.1.6 license: BSD3 license-file: LICENSE category: Development @@ -27,7 +27,7 @@ extra-doc-files: README.md CHANGES.txt -tested-with: GHC==8.8.1, GHC==8.6.5, GHC==8.4.4 +tested-with: GHC==8.10.1, GHC==8.8.3, GHC==8.6.5 source-repository head type: git @@ -48,6 +48,11 @@ manual: True description: Force dependency on ghc-lib-parser even if GHC API in the ghc package is supported +flag hsyaml + default: False + manual: True + description: Use HsYAML instead of yaml + library default-language: Haskell2010 build-depends: @@ -59,40 +64,47 @@ data-default >= 0.3, cpphs >= 1.20.1, cmdargs >= 0.10, - yaml >= 0.5.0, - haskell-src-exts >= 1.21 && < 1.24, - haskell-src-exts-util >= 0.2.5, uniplate >= 1.5, ansi-terminal >= 0.6.2, - extra >= 1.6.6, + extra >= 1.7.3, refact >= 0.3, aeson >= 1.1.2.0, - filepattern >= 0.1.1, - ghc-lib-parser-ex == 8.8.5.* - if !flag(ghc-lib) && impl(ghc >= 8.8.0) && impl(ghc < 8.9.0) - build-depends: - ghc == 8.8.*, - ghc-boot-th, - ghc-boot + filepattern >= 0.1.1 + + if !flag(ghc-lib) && impl(ghc >= 8.10.0) && impl(ghc < 8.11.0) + build-depends: + ghc == 8.10.*, + ghc-boot-th, + ghc-boot else - build-depends: - ghc-lib-parser == 8.8.* + build-depends: + ghc-lib-parser == 8.10.* + build-depends: + ghc-lib-parser-ex >= 8.10.0.14 && < 8.10.1 if flag(gpl) build-depends: hscolour >= 1.21 else cpp-options: -DGPL_SCARES_ME + if flag(hsyaml) + build-depends: + HsYAML >= 0.2, + HsYAML-aeson >= 0.2 + cpp-options: -DHS_YAML + else + build-depends: yaml >= 0.5.0 + hs-source-dirs: src exposed-modules: Language.Haskell.HLint - Language.Haskell.HLint3 - Language.Haskell.HLint4 other-modules: Paths_hlint Apply CmdLine Grep + Extension + Fixity HLint HsColour Idea @@ -109,29 +121,19 @@ Config.Type Config.Yaml + GHC.All GHC.Util GHC.Util.ApiAnnotation GHC.Util.View GHC.Util.Brackets + GHC.Util.DynFlags GHC.Util.FreeVars GHC.Util.HsDecl GHC.Util.HsExpr - GHC.Util.HsType - GHC.Util.Pat - GHC.Util.LanguageExtensions.Type - GHC.Util.Module - GHC.Util.Outputable GHC.Util.SrcLoc - GHC.Util.DynFlags - GHC.Util.RdrName GHC.Util.Scope GHC.Util.Unify - HSE.All - HSE.Match - HSE.Scope - HSE.Type - HSE.Util Hint.All Hint.Bracket Hint.Comment @@ -152,11 +154,11 @@ Hint.Smell Hint.Type Hint.Unsafe - Hint.Util Test.All Test.Annotations Test.InputOutput Test.Proof + Test.Summary Test.Translate Test.Util diff -Nru hlint-2.2.11/README.md hlint-3.1.6/README.md --- hlint-2.2.11/README.md 2020-01-27 16:14:58.000000000 +0000 +++ hlint-3.1.6/README.md 2020-06-14 18:45:05.000000000 +0000 @@ -14,7 +14,10 @@ * HLint operates on each module at a time in isolation, as a result HLint does not know about types or which names are in scope. * The presence of `seq` may cause some hints (i.e. eta-reduction) to change the semantics of a program. * Some transformed programs may require additional type signatures, particularly if the transformations trigger the monomorphism restriction or involve rank-2 types. +* Sometimes HLint will change the code in a way that causes values to default to different types, which may change the behaviour. +* HLint assumes duplicate identical expressions within in a single expression are used at the same type. * The `RebindableSyntax` extension can cause HLint to suggest incorrect changes. +* HLint can be configured with knowledge of C Pre Processor flags, but it can only see one conditional set of code at a time. * HLint turns on many language extensions so it can parse more documents, occasionally some break otherwise legal syntax - e.g. `{-#INLINE foo#-}` doesn't work with `MagicHash`, `foo $bar` means something different with `TemplateHaskell`. These extensions can be disabled with `-XNoMagicHash` or `-XNoTemplateHaskell` etc. * HLint doesn't run any custom preprocessors, e.g. [markdown-unlit](https://hackage.haskell.org/package/markdown-unlit) or [record-dot-preprocessor](https://hackage.haskell.org/package/record-dot-preprocessor), so code making use of them will usually fail to parse. @@ -83,6 +86,7 @@ * Lots of editors have HLint plugins (quite a few have more than one HLint plugin). * HLint is part of the multiple editor plugins [ghc-mod](https://hackage.haskell.org/package/ghc-mod) and [Intero](https://github.com/commercialhaskell/intero). * [HLint Source Plugin](https://github.com/ocharles/hlint-source-plugin) makes HLint available as a GHC plugin. +* [Splint](https://github.com/tfausak/splint) is another source plugin that doesn't require reparsing the GHC source if you are on the latest GHC version. * [Code Climate](https://docs.codeclimate.com/v1.0/docs/hlint) is a CI for analysis which integrates HLint. * [Danger](http://allocinit.io/haskell/danger-and-hlint/) can be used to automatically comment on pull requests with HLint suggestions. * [Restyled](https://restyled.io) includes an HLint Restyler to automatically run `hlint --refactor` on files changed in GitHub Pull Requests. @@ -92,26 +96,14 @@ ### Automatically Applying Hints -By supplying the `--refactor` flag hlint can automatically apply most -suggestions. Instead of a list of hints, hlint will instead output the -refactored file on stdout. In order to do this, it is necessary to have the -`refactor` executable on you path. `refactor` is provided by the -[`apply-refact`](https://github.com/mpickering/apply-refact) package, -it uses the GHC API in order to transform source files given a list of -refactorings to apply. Hlint directly calls the executable to apply the -suggestions. - -Additional configuration can be passed to `refactor` with the -`--refactor-options` flag. Some useful flags include `-i` which replaces the -original file and `-s` which asks for confirmation before performing a hint. +HLint can automatically apply some suggestions using the `--refactor` flag. If passed, instead of printing out the hints, HLint will output the refactored file on stdout. For `--refactor` to work it is necessary to have the `refactor` executable from the [`apply-refact`](https://github.com/mpickering/apply-refact) package on your `$PATH`. HLint uses that tool to perform the refactoring. -An alternative location for `refactor` can be specified with the -`--with-refactor` flag. +When using `--refactor` you can pass additional options to the `refactor` binary using `--refactor-options` flag. Some useful flags include `-i` (which replaces the original file) and `-s` (which asks for confirmation before performing a hint). The `--with-refactor` flag can be used to specify an alternative location for the `refactor` binary. Simple bindings for [Vim](https://github.com/mpickering/hlint-refactor-vim), [Emacs](https://github.com/mpickering/hlint-refactor-mode) and [Atom](https://github.com/mpickering/hlint-refactor-atom) are available. -Simple bindings for [vim](https://github.com/mpickering/hlint-refactor-vim), -[emacs](https://github.com/mpickering/hlint-refactor-mode) and [atom](https://github.com/mpickering/hlint-refactor-atom) are provided. +While the `--refactor` flag is useful, it is not complete or at the same level of quality as the rest of HLint: -There are no plans to support the duplication nor the renaming hints. +* Some hints don't generate refactorings. Examples include excess duplication, renaming hints and eta reduction hints. +* There are bugs in the underlying `refactor` tool which cause the resultant file to be incorrect. For example, `[1,2..3]` comes out as `[12..3]` ([#389](https://github.com/ndmitchell/hlint/issues/389)), even if there isn't a hint that touches it. ### Reports @@ -174,7 +166,7 @@ ### Why doesn't HLint know the fixity for my custom !@%$ operator? -HLint knows the fixities for all the operators in the base library, but no others. HLint works on a single file at a time, and does not resolve imports, so cannot see fixity declarations from imported modules. You can tell HLint about fixities by putting them in a hint file, or passing them on the command line. For example, pass `--with=infixr 5 !@%$`, or put all the fixity declarations in a `.hlint.yaml` file as `- fixity: "infixr 5 !@%$"`. You can also use [--find](https://rawgithub.com/ndmitchell/hlint/master/hlint.htm#find) to automatically produce a list of fixity declarations in a file. +HLint knows the fixities for all the operators in the base library, but no others. HLint works on a single file at a time, and does not resolve imports, so cannot see fixity declarations from imported modules. You can tell HLint about fixities by putting them in a hint file named `.hlint.yaml` with the syntax `- fixity: "infixr 5 !@%$"`. You can also use `--find` to automatically produce a list of fixity declarations in a file. ### Which hints are used? @@ -219,7 +211,7 @@ ### Why do I get a parse error? -HLint enables/disables a set of extensions designed to allow as many files to parse as possible, but sometimes you'll need to enable an additional extension (e.g. Arrows), or disable some (e.g. MagicHash) to enable your code to parse. In addition, sometimes the underlying parser library ([haskell-src-exts](https://github.com/haskell-suite/haskell-src-exts)) has a bug which causes a parse error. +HLint enables/disables a set of extensions designed to allow as many files to parse as possible, but sometimes you'll need to enable an additional extension (e.g. Arrows), or disable some (e.g. MagicHash) to enable your code to parse. ## Customizing the hints @@ -354,4 +346,4 @@ ### Acknowledgements -This program has only been made possible by the presence of the [haskell-src-exts](https://github.com/haskell-suite/haskell-src-exts) package, and many improvements have been made by [Niklas Broberg](http://www.nbroberg.se) in response to feature requests. Additionally, many people have provided help and patches, including Lennart Augustsson, Malcolm Wallace, Henk-Jan van Tuyl, Gwern Branwen, Alex Ott, Andy Stewart, Roman Leshchinskiy, Johannes Lippmann, Iustin Pop, Steve Purcell, Mitchell Rosen and others. +Many improvements to this program have been made by [Niklas Broberg](http://www.nbroberg.se) in response to feature requests. Additionally, many people have provided help and patches, including Lennart Augustsson, Malcolm Wallace, Henk-Jan van Tuyl, Gwern Branwen, Alex Ott, Andy Stewart, Roman Leshchinskiy, Johannes Lippmann, Iustin Pop, Steve Purcell, Mitchell Rosen and others. diff -Nru hlint-2.2.11/src/Apply.hs hlint-3.1.6/src/Apply.hs --- hlint-2.2.11/src/Apply.hs 2019-11-30 14:29:43.000000000 +0000 +++ hlint-3.1.6/src/Apply.hs 2020-06-14 18:45:05.000000000 +0000 @@ -3,9 +3,10 @@ import Control.Applicative import Data.Monoid -import HSE.All +import GHC.All import Hint.All import GHC.Util +import Data.Generics.Uniplate.DataOnly import Idea import Data.Tuple.Extra import Data.Either @@ -14,8 +15,9 @@ import Data.Ord import Config.Type import Config.Haskell -import HsSyn -import qualified SrcLoc as GHC +import SrcLoc +import GHC.Hs +import Language.Haskell.GhclibParserEx.GHC.Hs import qualified Data.HashSet as Set import Prelude @@ -24,7 +26,7 @@ applyHintFile :: ParseFlags -> [Setting] -> FilePath -> Maybe String -> IO [Idea] applyHintFile flags s file src = do res <- parseModuleApply flags s file src - return $ case res of + pure $ case res of Left err -> [err] Right m -> executeHints s [m] @@ -33,7 +35,7 @@ applyHintFiles :: ParseFlags -> [Setting] -> [FilePath] -> IO [Idea] applyHintFiles flags s files = do (err, ms) <- partitionEithers <$> mapM (\file -> parseModuleApply flags s file Nothing) files - return $ err ++ executeHints s ms + pure $ err ++ executeHints s ms -- | Given a way of classifying results, and a 'Hint', apply to a set of modules generating a list of 'Idea's. @@ -48,32 +50,28 @@ applyHintsReal :: [Setting] -> Hint -> [ModuleEx] -> [Idea] applyHintsReal settings hints_ ms = concat $ - [ map (classify classifiers . removeRequiresExtensionNotes (hseModule m)) $ + [ map (classify classifiers . removeRequiresExtensionNotes m) $ order [] (hintModule hints settings nm m) `merge` - concat [order [fromNamed d] $ decHints d | d <- moduleDecls (hseModule m)] `merge` - concat [order (maybeToList $ declName d) $ decHints' d | d <- hsmodDecls $ GHC.unLoc $ ghcModule m] - | (nm, m) <- mns - , let classifiers = cls ++ mapMaybe readPragma (universeBi (hseModule m)) ++ concatMap readComment (ghcComments m) + concat [order (maybeToList $ declName d) $ decHints d | d <- hsmodDecls $ unLoc $ ghcModule m] + | (nm,m) <- mns + , let classifiers = cls ++ mapMaybe readPragma (universeBi (ghcModule m)) ++ concatMap readComment (ghcComments m) , seq (length classifiers) True -- to force any errors from readPragma or readComment , let decHints = hintDecl hints settings nm m -- partially apply - , (nm',m') <- mns' - , let decHints' = hintDecl' hints settings nm' m' -- partially apply - , let order n = map (\i -> i{ideaModule= f $ moduleName (hseModule m) : ideaModule i, ideaDecl = f $ n ++ ideaDecl i}) . sortOn ideaSpan + , let order n = map (\i -> i{ideaModule = f $ modName (ghcModule m) : ideaModule i, ideaDecl = f $ n ++ ideaDecl i}) . sortOn ideaSpan , let merge = mergeBy (comparing ideaSpan)] ++ [map (classify cls) (hintModules hints settings mns)] where f = nubOrd . filter (/= "") cls = [x | SettingClassify x <- settings] - mns = map (\x -> (scopeCreate (hseModule x), x)) ms - mns' = map (\x -> (scopeCreate' (GHC.unLoc $ ghcModule x), x)) ms + mns = map (\x -> (scopeCreate (unLoc $ ghcModule x), x)) ms hints = (if length ms <= 1 then noModules else id) hints_ noModules h = h{hintModules = \_ _ -> []} `mappend` mempty{hintModule = \s a b -> hintModules h s [(a,b)]} -- If the hint has said you RequiresExtension Foo, but Foo is enabled, drop the note -removeRequiresExtensionNotes :: Module_ -> Idea -> Idea +removeRequiresExtensionNotes :: ModuleEx -> Idea -> Idea removeRequiresExtensionNotes m = \x -> x{ideaNote = filter keep $ ideaNote x} where - exts = Set.fromList $ map fromNamed $ moduleExtensions m + exts = Set.fromList $ concatMap snd $ languagePragmas $ pragmas $ ghcAnnotations m keep (RequiresExtension x) = not $ x `Set.member` exts keep _ = True @@ -87,9 +85,17 @@ parseModuleApply flags s file src = do res <- parseModuleEx (parseFlagsAddFixities [x | Infix x <- s] flags) file src case res of - Right r -> return $ Right r + Right r -> pure $ Right r Left (ParseError sl msg ctxt) -> - return $ Left $ classify [x | SettingClassify x <- s] $ rawIdeaN Error "Parse error" (mkSrcSpan sl sl) ctxt Nothing [] + pure $ Left $ classify [x | SettingClassify x <- s] $ rawIdeaN Error (adjustMessage msg) sl ctxt Nothing [] + where + -- important the message has "Parse error:" as the prefix so "--ignore=Parse error" works + -- try and tidy up things like "parse error (mismatched brackets)" to not look silly + adjustMessage :: String -> String + adjustMessage x = "Parse error: " ++ dropBrackets (dropPrefix "parse error " x) + + dropBrackets ('(':xs) | Just (xs,')') <- unsnoc xs = xs + dropBrackets xs = xs -- | Find which hints a list of settings implies. @@ -101,10 +107,11 @@ -- | Given some settings, make sure the severity field of the Idea is correct. classify :: [Classify] -> Idea -> Idea -classify xs i = let s = foldl' (f i) (ideaSeverity i) xs in s `seq` i{ideaSeverity=s} +classify xs i = let s = foldl' (f i) (ideaSeverity i) xs in s `seq` i{ideaSeverity=s} where -- figure out if we need to change the severity f :: Idea -> Severity -> Classify -> Severity - f i r c | classifyHint c ~= [ideaHint i] && classifyModule c ~= ideaModule i && classifyDecl c ~= ideaDecl i = classifySeverity c + f i r c | classifyHint c ~~= ideaHint i && classifyModule c ~= ideaModule i && classifyDecl c ~= ideaDecl i = classifySeverity c | otherwise = r - x ~= y = null x || x `elem` y + x ~= y = x == "" || x `elem` y + x ~~= y = x == "" || x == y || ((x ++ ":") `isPrefixOf` y) diff -Nru hlint-2.2.11/src/CC.hs hlint-3.1.6/src/CC.hs --- hlint-2.2.11/src/CC.hs 2019-09-10 21:12:21.000000000 +0000 +++ hlint-3.1.6/src/CC.hs 2020-03-04 12:10:52.000000000 +0000 @@ -13,13 +13,15 @@ import Data.Aeson (ToJSON(..), (.=), encode, object) import Data.Char (toUpper) import Data.Text (Text) -import Language.Haskell.Exts.SrcLoc (SrcSpan(..)) import qualified Data.Text as T import qualified Data.ByteString.Lazy.Char8 as C8 import Idea (Idea(..), Severity(..)) +import qualified SrcLoc as GHC +import qualified GHC.Util as GHC + data Issue = Issue { issueType :: Text , issueCheckName :: Text @@ -119,11 +121,11 @@ points Warning = 5 * basePoints points Error = 10 * basePoints -fromSrcSpan :: SrcSpan -> Location -fromSrcSpan SrcSpan{..} = Location +fromSrcSpan :: GHC.SrcSpan -> Location +fromSrcSpan GHC.SrcSpan{..} = Location (locationFileName srcSpanFilename) - (Position srcSpanStartLine srcSpanStartColumn) - (Position srcSpanEndLine srcSpanEndColumn) + (Position srcSpanStartLine' srcSpanStartColumn) + (Position srcSpanEndLine' srcSpanEndColumn) where locationFileName ('.':'/':x) = x locationFileName x = x diff -Nru hlint-2.2.11/src/CmdLine.hs hlint-3.1.6/src/CmdLine.hs --- hlint-2.2.11/src/CmdLine.hs 2020-01-21 14:43:12.000000000 +0000 +++ hlint-3.1.6/src/CmdLine.hs 2020-06-24 11:09:26.000000000 +0000 @@ -1,5 +1,5 @@ {-# LANGUAGE PatternGuards, DeriveDataTypeable, TupleSections #-} -{-# OPTIONS_GHC -fno-warn-missing-fields -fno-cse -O0 #-} +{-# OPTIONS_GHC -Wno-missing-fields -fno-cse -O0 #-} module CmdLine( Cmd(..), getCmd, @@ -8,20 +8,23 @@ ) where import Control.Monad.Extra +import Control.Exception.Extra import qualified Data.ByteString as BS import Data.Char -import Data.List +import Data.List.Extra import Data.Maybe import Data.Functor -import HSE.All(CppFlags(..)) -import Language.Haskell.Exts(defaultParseMode, baseLanguage) -import Language.Haskell.Exts.Extension +import GHC.All(CppFlags(..)) +import GHC.LanguageExtensions.Type +import Language.Haskell.GhclibParserEx.GHC.Driver.Session as GhclibParserEx +import DynFlags hiding (verbosity) + import Language.Preprocessor.Cpphs import System.Console.ANSI(hSupportsANSI) import System.Console.CmdArgs.Explicit(helpText, HelpFormat(..)) import System.Console.CmdArgs.Implicit import System.Directory.Extra -import System.Environment.Extra +import System.Environment import System.Exit import System.FilePath import System.IO @@ -32,6 +35,7 @@ import EmbedData import Util +import Extension import Paths_hlint import Data.Version import Prelude @@ -47,29 +51,28 @@ CmdMain{} -> dataDir =<< path =<< git =<< extension cmd CmdGrep{} -> path =<< extension cmd CmdTest{} -> dataDir cmd - _ -> return cmd where - path cmd = return $ if null $ cmdPath cmd then cmd{cmdPath=["."]} else cmd - extension cmd = return $ if null $ cmdExtension cmd then cmd{cmdExtension=["hs","lhs"]} else cmd + path cmd = pure $ if null $ cmdPath cmd then cmd{cmdPath=["."]} else cmd + extension cmd = pure $ if null $ cmdExtension cmd then cmd{cmdExtension=["hs","lhs"]} else cmd dataDir cmd - | cmdDataDir cmd /= "" = return cmd + | cmdDataDir cmd /= "" = pure cmd | otherwise = do x <- getDataDir b <- doesDirectoryExist x - if b then return cmd{cmdDataDir=x} else do + if b then pure cmd{cmdDataDir=x} else do exe <- getExecutablePath - return cmd{cmdDataDir = takeDirectory exe "data"} + pure cmd{cmdDataDir = takeDirectory exe "data"} git cmd | cmdGit cmd = do mgit <- findExecutable "git" case mgit of - Nothing -> error "Could not find git" + Nothing -> errorIO "Could not find git" Just git -> do let args = ["ls-files", "--cached", "--others", "--exclude-standard"] ++ map ("*." ++) (cmdExtension cmd) files <- readProcess git args "" - return cmd{cmdFiles = cmdFiles cmd ++ lines files} - | otherwise = return cmd + pure cmd{cmdFiles = cmdFiles cmd ++ lines files} + | otherwise = pure cmd exitWithHelp :: IO a @@ -95,7 +98,6 @@ {cmdFiles :: [FilePath] -- ^ which files to run it on, nothing = none given ,cmdReports :: [FilePath] -- ^ where to generate reports ,cmdGivenHints :: [FilePath] -- ^ which settignsfiles were explicitly given - ,cmdWithHints :: [String] -- ^ hints that are given on the command line ,cmdWithGroups :: [String] -- ^ groups that are given on the command line ,cmdGit :: Bool -- ^ use git ls-files to find files ,cmdColor :: ColorMode -- ^ color the result @@ -143,14 +145,11 @@ ,cmdGivenHints :: [FilePath] -- ^ which settings files were explicitly given ,cmdDataDir :: FilePath -- ^ the data directory ,cmdReports :: [FilePath] -- ^ where to generate reports - ,cmdWithHints :: [String] -- ^ hints that are given on the command line ,cmdTempDir :: FilePath -- ^ temporary directory to put the files in ,cmdQuickCheck :: Bool ,cmdTypeCheck :: Bool - } - | CmdHSE - {cmdFiles :: [FilePath] - ,cmdLanguage :: [String] -- ^ the extensions (may be prefixed by "No") + ,cmdWithRefactor :: FilePath + ,cmdGenerateSummary :: Bool -- ^ Generate a summary of built-in hints } deriving (Data,Typeable,Show) @@ -159,7 +158,6 @@ {cmdFiles = def &= args &= typ "FILE/DIR" ,cmdReports = nam "report" &= opt "report.html" &= typFile &= help "Generate a report in HTML" ,cmdGivenHints = nam "hint" &= typFile &= help "Hint/ignore file to use" - ,cmdWithHints = nam "with" &= typ "HINT" &= help "Extra hints to use" ,cmdWithGroups = nam_ "with-group" &= typ "GROUP" &= help "Extra hint groups to use" ,cmdGit = nam "git" &= help "Run on files tracked by git" ,cmdColor = nam "colour" &= name "color" &= opt Always &= typ "always/never/auto" &= help "Color output (requires ANSI terminal; auto means on when $TERM is supported; by itself, selects always)" @@ -199,13 +197,12 @@ ,cmdTypeCheck = nam_ "typecheck" &= help "Use GHC to type check the hints" ,cmdQuickCheck = nam_ "quickcheck" &= help "Use QuickCheck to check the hints" ,cmdTempDir = nam_ "tempdir" &= help "Where to put temporary files (not cleaned up)" + ,cmdGenerateSummary = nam_ "generate-summary" &= help "Generate a summary of built-in hints" } &= explicit &= name "test" &= details ["HLint gives hints on how to improve Haskell code." ,"" ,"To check all Haskell files in 'src' and generate a report type:" ," hlint src --report"] - ,CmdHSE - {} &= explicit &= name "hse" ] &= program "hlint" &= verbosity &= summary ("HLint v" ++ showVersion version ++ ", (C) Neil Mitchell 2006-2020") where @@ -213,42 +210,40 @@ nam_ xs = def &= explicit &= name xs -- | Where should we find the configuration files? --- * If someone passes cmdWithHints, only look at files they explicitly request --- * If someone passes an explicit hint name, automatically merge in data/hlint.yaml +-- Either we use the implicit search, or we follow the cmdGivenHints -- We want more important hints to go last, since they override cmdHintFiles :: Cmd -> IO [(FilePath, Maybe String)] cmdHintFiles cmd = do - let explicit1 = [hlintYaml | null $ cmdWithHints cmd] - let explicit2 = cmdGivenHints cmd - bad <- filterM (notM . doesFileExist) explicit2 - let explicit2' = map (,Nothing) explicit2 + let explicit = cmdGivenHints cmd + bad <- filterM (notM . doesFileExist) explicit when (bad /= []) $ fail $ unlines $ "Failed to find requested hint files:" : map (" "++) bad - if cmdWithHints cmd /= [] then return $ explicit1 ++ explicit2' else do + + -- if the user has given any explicit hints, ignore the local ones + implicit <- if explicit /= [] then pure Nothing else do -- we follow the stylish-haskell config file search policy -- 1) current directory or its ancestors; 2) home directory curdir <- getCurrentDirectory -- Ignores home directory when it isn't present. - home <- catchIOError ((:[]) <$> getHomeDirectory) (const $ return []) - implicit <- findM doesFileExist $ + home <- catchIOError ((:[]) <$> getHomeDirectory) (const $ pure []) + findM doesFileExist $ map ( ".hlint.yaml") (ancestors curdir ++ home) -- to match Stylish Haskell - ++ ["HLint.hs"] -- the default in HLint 1.* - return $ explicit1 ++ map (,Nothing) (maybeToList implicit) ++ explicit2' + pure $ hlintYaml : map (,Nothing) (maybeToList implicit ++ explicit) where ancestors = init . map joinPath . reverse . inits . splitPath -cmdExtensions :: Cmd -> (Language, [Extension]) +cmdExtensions :: Cmd -> (Maybe Language, ([Extension], [Extension])) cmdExtensions = getExtensions . cmdLanguage cmdCpp :: Cmd -> CppFlags cmdCpp cmd | cmdCppSimple cmd = CppSimple - | EnableExtension CPP `elem` snd (cmdExtensions cmd) = Cpphs defaultCpphsOptions + | Cpp `elem` (fst . snd) (cmdExtensions cmd) = Cpphs defaultCpphsOptions {boolopts=defaultBoolOptions{hashline=False, stripC89=True, ansi=cmdCppAnsi cmd} ,includes = cmdCppInclude cmd ,preInclude = cmdCppFile cmd - ,defines = ("__HLINT__","1") : [(a,drop 1 b) | x <- cmdCppDefine cmd, let (a,b) = break (== '=') x] + ,defines = ("__HLINT__","1") : [(a,drop1 b) | x <- cmdCppDefine cmd, let (a,b) = break (== '=') x] } | otherwise = NoCpp @@ -256,8 +251,8 @@ -- | Determines whether to use colour or not. cmdUseColour :: Cmd -> IO Bool cmdUseColour cmd = case cmdColor cmd of - Always -> return True - Never -> return False + Always -> pure True + Never -> pure False Auto -> hSupportsANSI stdout @@ -285,23 +280,23 @@ getFile :: (FilePath -> Bool) -> [FilePath] -> [String] -> Maybe FilePath -> FilePath -> IO [FilePath] getFile _ path _ (Just tmpfile) "-" = -- make sure we don't reencode any Unicode - BS.getContents >>= BS.writeFile tmpfile >> return [tmpfile] -getFile _ path _ Nothing "-" = return ["-"] + BS.getContents >>= BS.writeFile tmpfile >> pure [tmpfile] +getFile _ path _ Nothing "-" = pure ["-"] getFile _ [] exts _ file = exitMessage $ "Couldn't find file: " ++ file getFile ignore (p:ath) exts t file = do isDir <- doesDirectoryExist $ p <\> file if isDir then do let avoidDir x = let y = takeFileName x in "_" `isPrefixOf` y || ("." `isPrefixOf` y && not (all (== '.') y)) avoidFile x = let y = takeFileName x in "." `isPrefixOf` y || ignore x - xs <- listFilesInside (return . not . avoidDir) $ p <\> file - return [x | x <- xs, drop 1 (takeExtension x) `elem` exts, not $ avoidFile x] + xs <- listFilesInside (pure . not . avoidDir) $ p <\> file + pure [x | x <- xs, drop1 (takeExtension x) `elem` exts, not $ avoidFile x] else do isFil <- doesFileExist $ p <\> file - if isFil then return [p <\> file] + if isFil then pure [p <\> file] else do res <- getModule p exts file case res of - Just x -> return [x] + Just x -> pure [x] Nothing -> getFile ignore ath exts t file @@ -313,28 +308,32 @@ isMod _ = False pre = path <\> joinPath xs - f [] = return Nothing + f [] = pure Nothing f (x:xs) = do let s = pre <.> x b <- doesFileExist s - if b then return $ Just s else f xs -getModule _ _ _ = return Nothing + if b then pure $ Just s else f xs +getModule _ _ _ = pure Nothing -getExtensions :: [String] -> (Language, [Extension]) -getExtensions args = (lang, foldl f (if null langs then parseExtensions else []) exts) +getExtensions :: [String] -> (Maybe Language, ([Extension], [Extension])) +getExtensions args = + (lang, foldl f (if null langs then (defaultExtensions, []) else ([], [])) exts) where - lang = if null langs then baseLanguage defaultParseMode else fromJust $ lookup (last langs) ls + lang = if null langs then Nothing else Just $ fromJust $ lookup (last langs) ls (langs, exts) = partition (isJust . flip lookup ls) args - ls = [(show x, x) | x <- knownLanguages] - - f a "Haskell98" = [] - f a ('N':'o':x) | Just x <- readExtension x = delete x a - f a x | Just x <- readExtension x = x : delete x a - f a x = UnknownExtension x : delete (UnknownExtension x) a - + ls = [(show x, x) | x <- [Haskell98, Haskell2010]] -readExtension :: String -> Maybe Extension -readExtension x = case classifyExtension x of - UnknownExtension _ -> Nothing - x -> Just x + f (a, e) "Haskell98" = ([], []) + f (a, e) ('N':'o':x) | Just x <- GhclibParserEx.readExtension x, let xs = expandDisable x = + (deletes xs a, xs ++ deletes xs e) + f (a, e) x | Just x <- GhclibParserEx.readExtension x = (x : delete x a, delete x e) + f (a, e) x = (a, e) -- Ignore unknown extension. + + deletes [] ys = ys + deletes (x:xs) ys = deletes xs $ delete x ys + + -- if you disable a feature that implies another feature, sometimes we should disable both + -- e.g. no one knows what TemplateHaskellQuotes is https://github.com/ndmitchell/hlint/issues/1038 + expandDisable TemplateHaskell = [TemplateHaskell, TemplateHaskellQuotes] + expandDisable x = [x] diff -Nru hlint-2.2.11/src/Config/Compute.hs hlint-3.1.6/src/Config/Compute.hs --- hlint-2.2.11/src/Config/Compute.hs 2019-06-30 22:15:07.000000000 +0000 +++ hlint-3.1.6/src/Config/Compute.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,12 +1,23 @@ +{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-} -- | Given a file, guess settings from it by looking at the hints. module Config.Compute(computeSettings) where -import HSE.All +import GHC.All +import GHC.Util import Config.Type -import Config.Haskell -import Data.Monoid +import Fixity +import Data.Generics.Uniplate.DataOnly +import GHC.Hs hiding (Warning) +import RdrName +import Name +import Bag +import SrcLoc +import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances +import Language.Haskell.GhclibParserEx.GHC.Hs.Expr +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader import Prelude @@ -17,46 +28,55 @@ x <- parseModuleEx flags file Nothing case x of Left (ParseError sl msg _) -> - return ("# Parse error " ++ showSrcLoc sl ++ ": " ++ msg, []) - Right (ModuleEx m _ _ _) -> do - let xs = concatMap (findSetting $ UnQual an) (moduleDecls m) - r = concatMap (readSetting mempty) xs - s = unlines $ ["# hints found in " ++ file] ++ concatMap renderSetting r ++ ["# no hints found" | null xs] - return (s,r) + pure ("# Parse error " ++ showSrcSpan sl ++ ": " ++ msg, []) + Right ModuleEx{ghcModule=m} -> do + let xs = concatMap findSetting (hsmodDecls $ unLoc m) + s = unlines $ ["# hints found in " ++ file] ++ concatMap renderSetting xs ++ ["# no hints found" | null xs] + pure (s,xs) + renderSetting :: Setting -> [String] +-- Only need to convert the subset of Setting we generate renderSetting (SettingMatchExp HintRule{..}) = - ["- warn: {lhs: " ++ show (prettyPrint hintRuleLHS) ++ ", rhs: " ++ show (prettyPrint hintRuleRHS) ++ "}"] -renderSetting (Infix x) = ["- infix: " ++ show (prettyPrint (toInfixDecl x))] + ["- warn: {lhs: " ++ show (unsafePrettyPrint hintRuleLHS) ++ ", rhs: " ++ show (unsafePrettyPrint hintRuleRHS) ++ "}"] +renderSetting (Infix x) = + ["- fixity: " ++ show (unsafePrettyPrint $ toFixitySig x)] renderSetting _ = [] -findSetting :: (Name S -> QName S) -> Decl_ -> [Decl_] -findSetting qual (InstDecl _ _ _ (Just xs)) = concatMap (findSetting qual) [x | InsDecl _ x <- xs] -findSetting qual (PatBind _ (PVar _ name) (UnGuardedRhs _ bod) Nothing) = findExp (qual name) [] bod -findSetting qual (FunBind _ [InfixMatch _ p1 name ps rhs bind]) = findSetting qual $ FunBind an [Match an name (p1:ps) rhs bind] -findSetting qual (FunBind _ [Match _ name ps (UnGuardedRhs _ bod) Nothing]) = findExp (qual name) [] $ Lambda an ps bod -findSetting _ x@InfixDecl{} = [x] -findSetting _ _ = [] - - --- given a result function name, a list of variables, a body expression, give some hints -findExp :: QName S -> [String] -> Exp_ -> [Decl_] -findExp name vs (Lambda _ ps bod) | length ps2 == length ps = findExp name (vs++ps2) bod - | otherwise = [] - where ps2 = [x | PVar_ x <- map view ps] -findExp name vs Var{} = [] -findExp name vs (InfixApp _ x dot y) | isDot dot = findExp name (vs++["_hlint"]) $ App an x $ Paren an $ App an y (toNamed "_hlint") - -findExp name vs bod = [PatBind an (toNamed "warn") (UnGuardedRhs an $ InfixApp an lhs (toNamed "==>") rhs) Nothing] +findSetting :: LHsDecl GhcPs -> [Setting] +findSetting (L _ (ValD _ x)) = findBind x +findSetting (L _ (InstD _ (ClsInstD _ ClsInstDecl{cid_binds}))) = + concatMap (findBind . unLoc) $ bagToList cid_binds +findSetting (L _ (SigD _ (FixSig _ x))) = map Infix $ fromFixitySig x +findSetting x = [] + + +findBind :: HsBind GhcPs -> [Setting] +findBind VarBind{var_id, var_rhs} = findExp var_id [] $ unLoc var_rhs +findBind FunBind{fun_id, fun_matches} = findExp (unLoc fun_id) [] $ HsLam noExtField fun_matches +findBind _ = [] + +findExp :: IdP GhcPs -> [String] -> HsExpr GhcPs -> [Setting] +findExp name vs (HsLam _ MG{mg_alts=L _ [L _ Match{m_pats, m_grhss=GRHSs{grhssGRHSs=[L _ (GRHS _ [] x)], grhssLocalBinds=L _ (EmptyLocalBinds _)}}]}) + = if length m_pats == length ps then findExp name (vs++ps) $ unLoc x else [] + where ps = [rdrNameStr x | L _ (VarPat _ x) <- m_pats] +findExp name vs HsLam{} = [] +findExp name vs HsVar{} = [] +findExp name vs (OpApp _ x dot y) | isDot dot = findExp name (vs++["_hlint"]) $ + HsApp noExtField x $ noLoc $ HsPar noExtField $ noLoc $ HsApp noExtField y $ noLoc $ mkVar "_hlint" + +findExp name vs bod = [SettingMatchExp $ + HintRule Warning defaultHintName [] + mempty (extendInstances lhs) (extendInstances $ fromParen rhs) Nothing] where - lhs = g $ transform f bod - rhs = apps $ Var an name : map snd rep + lhs = fromParen $ noLoc $ transform f bod + rhs = apps $ map noLoc $ HsVar noExtField (noLoc name) : map snd rep - rep = zip vs $ map (toNamed . return) ['a'..] - f xx | Var_ x <- view xx, Just y <- lookup x rep = y - f (InfixApp _ x dol y) | isDol dol = App an x (paren y) + rep = zip vs $ map (mkVar . pure) ['a'..] + f (HsVar _ x) | Just y <- lookup (rdrNameStr x) rep = y + f (OpApp _ x dol y) | isDol dol = HsApp noExtField x $ noLoc $ HsPar noExtField y f x = x - g o@(InfixApp _ _ _ x) | isAnyApp x || isAtom x = o - g o@App{} = o - g o = paren o + +mkVar :: String -> HsExpr GhcPs +mkVar = HsVar noExtField . noLoc . Unqual . mkVarOcc diff -Nru hlint-2.2.11/src/Config/Haskell.hs hlint-3.1.6/src/Config/Haskell.hs --- hlint-2.2.11/src/Config/Haskell.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/Config/Haskell.hs 2020-05-27 09:22:13.000000000 +0000 @@ -1,96 +1,53 @@ -{-# LANGUAGE PatternGuards, ViewPatterns, ScopedTypeVariables, TupleSections #-} +{-# LANGUAGE PatternGuards, ViewPatterns, TupleSections #-} module Config.Haskell( readPragma, - readComment, - readSetting, - readFileConfigHaskell + readComment ) where -import HSE.All import Data.Char import Data.List.Extra -import Text.Read.Extra(readMaybe) +import Text.Read import Data.Tuple.Extra import Data.Maybe import Config.Type import Util import Prelude -import qualified HsSyn as GHC -import qualified BasicTypes as GHC import GHC.Util -import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances -import SrcLoc as GHC +import SrcLoc +import GHC.Hs.Extension +import GHC.Hs.Decls hiding (SpliceDecl) +import GHC.Hs.Expr hiding (Match) +import GHC.Hs.Lit +import FastString import ApiAnnotation +import Outputable - -addInfix :: ParseFlags -> ParseFlags -addInfix = parseFlagsAddFixities $ infix_ (-1) ["==>"] - - ---------------------------------------------------------------------- --- READ A SETTINGS FILE - -readFileConfigHaskell :: FilePath -> Maybe String -> IO [Setting] -readFileConfigHaskell file contents = do - let flags = addInfix defaultParseFlags - res <- parseModuleEx flags file contents - case res of - Left (ParseError sl msg err) -> - error $ "Config parse failure at " ++ showSrcLoc sl ++ ": " ++ msg ++ "\n" ++ err - Right modEx@(ModuleEx m _ _ _) -> return $ readSettings m ++ map SettingClassify (concatMap readComment (ghcComments modEx)) - - --- | Given a module containing HLint settings information return the 'Classify' rules and the 'HintRule' expressions. --- Any fixity declarations will be discarded, but any other unrecognised elements will result in an exception. -readSettings :: Module_ -> [Setting] -readSettings m = concatMap (readSetting $ scopeCreate m) $ concatMap getEquations $ - [AnnPragma l x | AnnModulePragma l x <- modulePragmas m] ++ moduleDecls m - - -readSetting :: Scope -> Decl_ -> [Setting] -readSetting s (FunBind _ [Match _ (Ident _ (getSeverity -> Just severity)) pats (UnGuardedRhs _ bod) bind]) - | InfixApp _ lhs op rhs <- bod, opExp op ~= "==>" = - let (a,b) = readSide $ childrenBi bind in - let unit = GHC.noLoc $ GHC.ExplicitTuple GHC.noExt [] GHC.Boxed in - [SettingMatchExp $ - HintRule severity (head $ snoc names defaultHintName) s (fromParen lhs) (fromParen rhs) a b - -- Todo : Replace these with "proper" GHC expressions. - (extendInstances mempty) (extendInstances unit) (extendInstances unit) Nothing] - | otherwise = [SettingClassify $ Classify severity n a b | n <- names2, (a,b) <- readFuncs bod] - where - names = filter (not . null) $ getNames pats bod - names2 = ["" | null names] ++ names - -readSetting s x | "test" `isPrefixOf` map toLower (fromNamed x) = [] -readSetting s (AnnPragma _ x) | Just y <- readPragma x = [SettingClassify y] -readSetting s (PatBind an (PVar _ name) bod bind) = readSetting s $ FunBind an [Match an name [] bod bind] -readSetting s (FunBind an xs) | length xs /= 1 = concatMap (readSetting s . FunBind an . return) xs -readSetting s (SpliceDecl an (App _ (Var _ x) (Lit _ y))) = readSetting s $ FunBind an [Match an (toNamed $ fromNamed x) [PLit an (Signless an) y] (UnGuardedRhs an $ Lit an $ String an "" "") Nothing] -readSetting s x@InfixDecl{} = map Infix $ getFixity x -readSetting s x = errorOn x "bad hint" - +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader -- | Read an {-# ANN #-} pragma and determine if it is intended for HLint. -- Return Nothing if it is not an HLint pragma, otherwise what it means. -readPragma :: Annotation S -> Maybe Classify -readPragma o = case o of - Ann _ name x -> f (fromNamed name) x - TypeAnn _ name x -> f (fromNamed name) x - ModuleAnn _ x -> f "" x +readPragma :: AnnDecl GhcPs -> Maybe Classify +readPragma (HsAnnotation _ _ provenance expr) = f expr where - f name (Lit _ (String _ s _)) | "hlint:" `isPrefixOf` map toLower s = + name = case provenance of + ValueAnnProvenance (L _ x) -> occNameStr x + TypeAnnProvenance (L _ x) -> occNameStr x + ModuleAnnProvenance -> "" + + f (L _ (HsLit _ (HsString _ (unpackFS -> s)))) | "hlint:" `isPrefixOf` lower s = case getSeverity a of - Nothing -> errorOn o "bad classify pragma" + Nothing -> errorOn expr "bad classify pragma" Just severity -> Just $ Classify severity (trimStart b) "" name where (a,b) = break isSpace $ trimStart $ drop 6 s - f name (Paren _ x) = f name x - f name (ExpTypeSig _ x _) = f name x - f _ _ = Nothing - + f (L _ (HsPar _ x)) = f x + f (L _ (ExprWithTySig _ x _)) = f x + f _ = Nothing +readPragma _ = Nothing -readComment :: GHC.Located AnnotationComment -> [Classify] +readComment :: Located AnnotationComment -> [Classify] readComment c@(L pos AnnBlockComment{}) | (hash, x) <- maybe (False, x) (True,) $ stripPrefix "#" x , x <- trim x @@ -117,60 +74,15 @@ readComment _ = [] -readSide :: [Decl_] -> (Maybe Exp_, [Note]) -readSide = foldl f (Nothing,[]) - where f (Nothing,notes) (PatBind _ PWildCard{} (UnGuardedRhs _ side) Nothing) = (Just side, notes) - f (Nothing,notes) (PatBind _ (fromNamed -> "side") (UnGuardedRhs _ side) Nothing) = (Just side, notes) - f (side,[]) (PatBind _ (fromNamed -> "note") (UnGuardedRhs _ note) Nothing) = (side,g note) - f _ x = errorOn x "bad side condition" - - g (Lit _ (String _ x _)) = [Note x] - g (List _ xs) = concatMap g xs - g x = case fromApps x of - [con -> Just "IncreasesLaziness"] -> [IncreasesLaziness] - [con -> Just "DecreasesLaziness"] -> [DecreasesLaziness] - [con -> Just "RemovesError",fromString -> Just a] -> [RemovesError a] - [con -> Just "ValidInstance",fromString -> Just a,var -> Just b] -> [ValidInstance a b] - [con -> Just "RequiresExtension",con -> Just a] -> [RequiresExtension a] - _ -> errorOn x "bad note" - - con :: Exp_ -> Maybe String - con c@Con{} = Just $ prettyPrint c; con _ = Nothing - var c@Var{} = Just $ prettyPrint c; var _ = Nothing - - --- Note: Foo may be ("","Foo") or ("Foo",""), return both -readFuncs :: Exp_ -> [(String, String)] -readFuncs (App _ x y) = readFuncs x ++ readFuncs y -readFuncs (Lit _ (String _ "" _)) = [("","")] -readFuncs (Var _ (UnQual _ name)) = [("",fromNamed name)] -readFuncs (Var _ (Qual _ (ModuleName _ mod) name)) = [(mod, fromNamed name)] -readFuncs (Con _ (UnQual _ name)) = [(fromNamed name,""),("",fromNamed name)] -readFuncs (Con _ (Qual _ (ModuleName _ mod) name)) = [(mod ++ "." ++ fromNamed name,""),(mod,fromNamed name)] -readFuncs x = errorOn x "bad classification rule" - - -getNames :: [Pat_] -> Exp_ -> [String] -getNames ps _ | ps /= [], Just ps <- mapM fromPString ps = ps -getNames [] (InfixApp _ lhs op rhs) | opExp op ~= "==>" = map ("Use "++) names - where - lnames = map f $ childrenS lhs - rnames = map f $ childrenS rhs - names = filter (not . isUnifyVar) $ (rnames \\ lnames) ++ rnames - f (Ident _ x) = x - f (Symbol _ x) = x -getNames _ _ = [] - - -errorOn :: (Annotated ast, Pretty (ast S)) => ast S -> String -> b -errorOn val msg = exitMessageImpure $ - showSrcLoc (getPointLoc $ ann val) ++ +errorOn :: Outputable a => Located a -> String -> b +errorOn (L pos val) msg = exitMessageImpure $ + showSrcSpan pos ++ ": Error while reading hint file, " ++ msg ++ "\n" ++ - prettyPrint val + unsafePrettyPrint val -errorOnComment :: GHC.Located AnnotationComment -> String -> b +errorOnComment :: Located AnnotationComment -> String -> b errorOnComment c@(L s _) msg = exitMessageImpure $ let isMultiline = isCommentMultiline c in - showSrcLoc (ghcSrcLocToHSE $ GHC.srcSpanStart s) ++ + showSrcSpan s ++ ": Error while reading hint file, " ++ msg ++ "\n" ++ (if isMultiline then "{-" else "--") ++ commentText c ++ (if isMultiline then "-}" else "") diff -Nru hlint-2.2.11/src/Config/Read.hs hlint-3.1.6/src/Config/Read.hs --- hlint-2.2.11/src/Config/Read.hs 2017-09-24 20:02:28.000000000 +0000 +++ hlint-3.1.6/src/Config/Read.hs 2020-03-12 06:22:38.000000000 +0000 @@ -2,7 +2,8 @@ module Config.Read(readFilesConfig) where import Config.Type -import Config.Haskell +import Control.Monad +import Control.Exception.Extra import Config.Yaml import Data.List.Extra import System.FilePath @@ -10,8 +11,11 @@ readFilesConfig :: [(FilePath, Maybe String)] -> IO [Setting] readFilesConfig files = do - yaml <- mapM (uncurry readFileConfigYaml) yaml - haskell <- mapM (uncurry readFileConfigHaskell) haskell - return $ concat haskell ++ settingsFromConfigYaml yaml - where - (yaml, haskell) = partition (\(x,_) -> lower (takeExtension x) `elem` [".yml",".yaml"]) files + let (yaml, haskell) = partition (\(x,_) -> lower (takeExtension x) `elem` [".yml",".yaml"]) files + unless (null haskell) $ + errorIO $ "HLint 2.3 and beyond cannot use Haskell configuration files.\n" ++ + "Tried to use: " ++ show haskell ++ "\n" ++ + "Convert it to .yaml file format, following the example at\n" ++ + " " + yaml <- mapM (uncurry readFileConfigYaml) yaml + pure $ settingsFromConfigYaml yaml diff -Nru hlint-2.2.11/src/Config/Type.hs hlint-3.1.6/src/Config/Type.hs --- hlint-2.2.11/src/Config/Type.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/Config/Type.hs 2020-03-28 20:54:21.000000000 +0000 @@ -5,12 +5,13 @@ defaultHintName, isUnifyVar, showNotes, getSeverity, getRestrictType, getSmellType ) where -import HSE.All import Data.Char import Data.List.Extra import Prelude -import qualified HsSyn + +import qualified GHC.Hs +import Fixity import GHC.Util import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances @@ -40,7 +41,7 @@ = Ignore -- ^ The issue has been explicitly ignored and will usually be hidden (pass @--show@ on the command line to see ignored ideas). | Suggestion -- ^ Suggestions are things that some people may consider improvements, but some may not. | Warning -- ^ Warnings are suggestions that are nearly always a good idea to apply. - | Error -- ^ Available as a setting for the user. + | Error -- ^ Available as a setting for the user. Only parse errors have this setting by default. deriving (Eq,Ord,Show,Read,Bounded,Enum) @@ -94,16 +95,12 @@ data HintRule = HintRule {hintRuleSeverity :: Severity -- ^ Default severity for the hint. ,hintRuleName :: String -- ^ Name for the hint. - ,hintRuleScope :: Scope -- ^ Module scope in which the hint operates. - ,hintRuleLHS :: Exp SrcSpanInfo -- ^ LHS - ,hintRuleRHS :: Exp SrcSpanInfo -- ^ RHS - ,hintRuleSide :: Maybe (Exp SrcSpanInfo) -- ^ Side condition, typically specified with @where _ = ...@. ,hintRuleNotes :: [Note] -- ^ Notes about application of the hint. + ,hintRuleScope :: Scope -- ^ Module scope in which the hint operates (GHC parse tree). -- We wrap these GHC elements in 'HsExtendInstances' in order that we may derive 'Show'. - ,hintRuleGhcScope :: HsExtendInstances Scope' -- ^ Module scope in which the hint operates (GHC parse tree). - ,hintRuleGhcLHS :: HsExtendInstances (HsSyn.LHsExpr HsSyn.GhcPs) -- ^ LHS (GHC parse tree). - ,hintRuleGhcRHS :: HsExtendInstances (HsSyn.LHsExpr HsSyn.GhcPs) -- ^ RHS (GHC parse tree). - ,hintRuleGhcSide :: Maybe (HsExtendInstances (HsSyn.LHsExpr HsSyn.GhcPs)) -- ^ Side condition (GHC parse tree). + ,hintRuleLHS :: HsExtendInstances (GHC.Hs.LHsExpr GHC.Hs.GhcPs) -- ^ LHS (GHC parse tree). + ,hintRuleRHS :: HsExtendInstances (GHC.Hs.LHsExpr GHC.Hs.GhcPs) -- ^ RHS (GHC parse tree). + ,hintRuleSide :: Maybe (HsExtendInstances (GHC.Hs.LHsExpr GHC.Hs.GhcPs)) -- ^ Side condition (GHC parse tree). } deriving Show @@ -136,5 +133,5 @@ | SettingArgument String -- ^ Extra command-line argument | SettingSmell SmellType Int | Builtin String -- use a builtin hint set - | Infix Fixity + | Infix FixityInfo deriving Show diff -Nru hlint-2.2.11/src/Config/Yaml.hs hlint-3.1.6/src/Config/Yaml.hs --- hlint-2.2.11/src/Config/Yaml.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/Config/Yaml.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,4 +1,6 @@ +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings, ViewPatterns, RecordWildCards, GeneralizedNewtypeDeriving, TupleSections #-} +{-# LANGUAGE CPP #-} module Config.Yaml( ConfigYaml, @@ -7,30 +9,65 @@ ) where import Config.Type -import Data.Yaml import Data.Either import Data.Maybe import Data.List.Extra import Data.Tuple.Extra import Control.Monad.Extra -import Control.Exception.Extra import qualified Data.Text as T import qualified Data.Vector as V import qualified Data.ByteString.Char8 as BS import qualified Data.HashMap.Strict as Map -import HSE.All hiding (Rule, String) +import Data.Generics.Uniplate.DataOnly +import GHC.All +import Fixity +import Extension +import Module import Data.Functor import Data.Semigroup import Timing -import Util import Prelude -import qualified Lexer as GHC -import qualified ErrUtils -import qualified Outputable -import qualified HsSyn -import GHC.Util (baseDynFlags, Scope',scopeCreate') +import Bag +import Lexer +import ErrUtils hiding (Severity) +import Outputable +import GHC.Hs +import SrcLoc +import RdrName +import OccName +import GHC.Util (baseDynFlags, Scope, scopeCreate) import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader +import Data.Char + +#ifdef HS_YAML + +import Data.YAML (Pos) +import Data.YAML.Aeson (encode1Strict, decode1Strict) +import Data.Aeson hiding (encode) +import Data.Aeson.Types (Parser) +import qualified Data.ByteString as BSS + +decodeFileEither :: FilePath -> IO (Either (Pos, String) ConfigYaml) +decodeFileEither path = decode1Strict <$> BSS.readFile path + +decodeEither' :: BSS.ByteString -> Either (Pos, String) ConfigYaml +decodeEither' = decode1Strict + +displayException :: (Pos, String) -> String +displayException = show + +encode :: Value -> BSS.ByteString +encode = encode1Strict + +#else + +import Data.Yaml +import Control.Exception.Extra + +#endif + -- | Read a config file in YAML format. Takes a filename, and optionally the contents. -- Fails if the YAML doesn't parse or isn't valid HLint YAML @@ -38,10 +75,10 @@ readFileConfigYaml file contents = timedIO "Config" file $ do val <- case contents of Nothing -> decodeFileEither file - Just src -> return $ decodeEither' $ BS.pack src + Just src -> pure $ decodeEither' $ BS.pack src case val of Left e -> fail $ "Failed to read YAML configuration file " ++ file ++ "\n " ++ displayException e - Right v -> return v + Right v -> pure v --------------------------------------------------------------------- @@ -57,15 +94,13 @@ data Package = Package {packageName :: String - ,packageModules :: [ImportDecl S] - ,packageGhcModules :: [HsExtendInstances (HsSyn.LImportDecl HsSyn.GhcPs)] + ,packageModules :: [HsExtendInstances (LImportDecl GhcPs)] } deriving Show data Group = Group {groupName :: String ,groupEnabled :: Bool - ,groupImports :: [Either String (ImportDecl S)] -- Left for package imports - ,groupGhcImports :: [Either String (HsExtendInstances (HsSyn.LImportDecl HsSyn.GhcPs))] + ,groupImports :: [Either String (HsExtendInstances (LImportDecl GhcPs))] ,groupRules :: [Either HintRule Classify] -- HintRule has scope set to mempty } deriving Show @@ -99,11 +134,11 @@ dotDot x = let (a,b) = BS.splitAt 250 x in BS.unpack a ++ (if BS.null b then "" else "...") parseArray :: Val -> Parser [Val] -parseArray v@(getVal -> Array xs) = concatMapM parseArray $ zipWith (\i x -> addVal (show i) x v) [0..] $ V.toList xs -parseArray v = return [v] +parseArray v@(getVal -> Array xs) = concatMapM parseArray $ zipWithFrom (\i x -> addVal (show i) x v) 0 $ V.toList xs +parseArray v = pure [v] parseObject :: Val -> Parser (Map.HashMap T.Text Value) -parseObject (getVal -> Object x) = return x +parseObject (getVal -> Object x) = pure x parseObject v = parseFail v "Expected an Object" parseObject1 :: Val -> Parser (String, Val) @@ -114,7 +149,7 @@ _ -> parseFail v $ "Expected exactly one key but got " ++ show (Map.size mp) parseString :: Val -> Parser String -parseString (getVal -> String x) = return $ T.unpack x +parseString (getVal -> String x) = pure $ T.unpack x parseString v = parseFail v "Expected a String" parseInt :: Val -> Parser Int @@ -125,11 +160,11 @@ parseArrayString = parseArray >=> mapM parseString maybeParse :: (Val -> Parser a) -> Maybe Val -> Parser (Maybe a) -maybeParse parseValue Nothing = return Nothing +maybeParse parseValue Nothing = pure Nothing maybeParse parseValue (Just value) = Just <$> parseValue value parseBool :: Val -> Parser Bool -parseBool (getVal -> Bool b) = return b +parseBool (getVal -> Bool b) = pure b parseBool v = parseFail v "Expected a Bool" parseField :: String -> Val -> Parser Val @@ -137,14 +172,14 @@ x <- parseFieldOpt s v case x of Nothing -> parseFail v $ "Expected a field named " ++ s - Just v -> return v + Just v -> pure v parseFieldOpt :: String -> Val -> Parser (Maybe Val) parseFieldOpt s v = do mp <- parseObject v case Map.lookup (T.pack s) mp of - Nothing -> return Nothing - Just x -> return $ Just $ addVal s x v + Nothing -> pure Nothing + Just x -> pure $ Just $ addVal s x v allowFields :: Val -> [String] -> Parser () allowFields v allow = do @@ -153,29 +188,22 @@ when (bad /= []) $ parseFail v $ "Not allowed keys: " ++ unwords bad -parseHSE :: (ParseMode -> String -> ParseResult v) -> Val -> Parser v -parseHSE parser v = do - x <- parseString v - case parser defaultParseMode{extensions=configExtensions} x of - ParseOk x -> return x - ParseFailed loc s -> - parseFail v $ "Failed to parse " ++ s ++ ", when parsing:\n " ++ x - -parseGHC :: (ParseMode -> String -> GHC.ParseResult v) -> Val -> Parser v +parseGHC :: (ParseFlags -> String -> ParseResult v) -> Val -> Parser v parseGHC parser v = do x <- parseString v - case parser defaultParseMode{extensions=configExtensions} x of - GHC.POk _ x -> return x - GHC.PFailed _ loc err -> - let msg = Outputable.showSDoc baseDynFlags $ - ErrUtils.pprLocErrMsg (ErrUtils.mkPlainErrMsg baseDynFlags loc err) + case parser defaultParseFlags{enabledExtensions=configExtensions, disabledExtensions=[]} x of + POk _ x -> pure x + PFailed ps -> + let (_, errs) = getMessages ps baseDynFlags + errMsg = head (bagToList errs) + msg = Outputable.showSDoc baseDynFlags $ ErrUtils.pprLocErrMsg errMsg in parseFail v $ "Failed to parse " ++ msg ++ ", when parsing:\n " ++ x --------------------------------------------------------------------- -- YAML TO DATA TYPE instance FromJSON ConfigYaml where - parseJSON Null = return mempty + parseJSON Null = pure mempty parseJSON x = parseConfigYaml $ newVal x parseConfigYaml :: Val -> Parser ConfigYaml @@ -197,15 +225,14 @@ parsePackage :: Val -> Parser Package parsePackage v = do packageName <- parseField "name" v >>= parseString - packageModules <- parseField "modules" v >>= parseArray >>= mapM (parseHSE parseImportDeclWithMode) - packageGhcModules <- parseField "modules" v >>= parseArray >>= mapM (fmap extendInstances <$> parseGHC parseImportDeclGhcWithMode) + packageModules <- parseField "modules" v >>= parseArray >>= mapM (fmap extendInstances <$> parseGHC parseImportDeclGhcWithMode) allowFields v ["name","modules"] - return Package{..} + pure Package{..} parseFixity :: Val -> Parser [Setting] -parseFixity v = parseArray v >>= concatMapM (parseHSE parseDeclWithMode >=> f) +parseFixity v = parseArray v >>= concatMapM (parseGHC parseDeclGhcWithMode >=> f) where - f x@InfixDecl{} = return $ map Infix $ getFixity x + f (L _ (SigD _ (FixSig _ x))) = pure $ map Infix $ fromFixitySig x f _ = parseFail v "Expected fixity declaration" parseSmell :: Val -> Parser [Setting] @@ -213,59 +240,48 @@ smellName <- parseField "type" v >>= parseString smellType <- require v "Expected SmellType" $ getSmellType smellName smellLimit <- parseField "limit" v >>= parseInt - return [SettingSmell smellType smellLimit] + pure [SettingSmell smellType smellLimit] where require :: Val -> String -> Maybe a -> Parser a - require _ _ (Just a) = return a + require _ _ (Just a) = pure a require val err Nothing = parseFail val err parseGroup :: Val -> Parser Group parseGroup v = do groupName <- parseField "name" v >>= parseString - groupEnabled <- parseFieldOpt "enabled" v >>= maybe (return True) parseBool - groupImports <- parseFieldOpt "imports" v >>= maybe (return []) (parseArray >=> mapM parseImport) - groupGhcImports <- parseFieldOpt "imports" v >>= maybe (return []) (parseArray >=> mapM parseImportGHC) - groupRules <- parseFieldOpt "rules" v >>= maybe (return []) parseArray >>= concatMapM parseRule + groupEnabled <- parseFieldOpt "enabled" v >>= maybe (pure True) parseBool + groupImports <- parseFieldOpt "imports" v >>= maybe (pure []) (parseArray >=> mapM parseImport) + groupRules <- parseFieldOpt "rules" v >>= maybe (pure []) parseArray >>= concatMapM parseRule allowFields v ["name","enabled","imports","rules"] - return Group{..} + pure Group{..} where parseImport v = do x <- parseString v case word1 x of - ("package", x) -> return $ Left x - _ -> Right <$> parseHSE parseImportDeclWithMode v - parseImportGHC v = do - x <- parseString v - case word1 x of - ("package", x) -> return $ Left x + ("package", x) -> pure $ Left x _ -> Right . extendInstances <$> parseGHC parseImportDeclGhcWithMode v ruleToGroup :: [Either HintRule Classify] -> Group -ruleToGroup = Group "" True [] [] +ruleToGroup = Group "" True [] parseRule :: Val -> Parser [Either HintRule Classify] parseRule v = do (severity, v) <- parseSeverityKey v isRule <- isJust <$> parseFieldOpt "lhs" v if isRule then do - hintRuleLHS <- parseField "lhs" v >>= parseHSE parseExpWithMode - hintRuleRHS <- parseField "rhs" v >>= parseHSE parseExpWithMode - hintRuleNotes <- parseFieldOpt "note" v >>= maybe (return []) (fmap (map asNote) . parseArrayString) - hintRuleName <- parseFieldOpt "name" v >>= maybe (return $ guessName hintRuleLHS hintRuleRHS) parseString - hintRuleSide <- parseFieldOpt "side" v >>= maybe (return Nothing) (fmap Just . parseHSE parseExpWithMode) - - hintRuleGhcLHS <- parseField "lhs" v >>= fmap extendInstances . parseGHC parseExpGhcWithMode - hintRuleGhcRHS <- parseField "rhs" v >>= fmap extendInstances . parseGHC parseExpGhcWithMode - hintRuleGhcSide <- parseFieldOpt "side" v >>= maybe (return Nothing) (fmap (Just . extendInstances) . parseGHC parseExpGhcWithMode) + hintRuleNotes <- parseFieldOpt "note" v >>= maybe (pure []) (fmap (map asNote) . parseArrayString) + lhs <- parseField "lhs" v >>= parseGHC parseExpGhcWithMode + rhs <- parseField "rhs" v >>= parseGHC parseExpGhcWithMode + hintRuleSide <- parseFieldOpt "side" v >>= maybe (pure Nothing) (fmap (Just . extendInstances) . parseGHC parseExpGhcWithMode) + hintRuleName <- parseFieldOpt "name" v >>= maybe (pure $ guessName lhs rhs) parseString allowFields v ["lhs","rhs","note","name","side"] - let hintRuleScope = mempty :: Scope - let hintRuleGhcScope = extendInstances mempty :: HsExtendInstances Scope' - return [Left HintRule{hintRuleSeverity=severity, ..}] + let hintRuleScope = mempty + pure [Left HintRule{hintRuleSeverity=severity,hintRuleLHS=extendInstances lhs,hintRuleRHS=extendInstances rhs, ..}] else do - names <- parseFieldOpt "name" v >>= maybe (return []) parseArrayString - within <- parseFieldOpt "within" v >>= maybe (return [("","")]) (parseArray >=> concatMapM parseWithin) - return [Right $ Classify severity n a b | (a,b) <- within, n <- ["" | null names] ++ names] + names <- parseFieldOpt "name" v >>= maybe (pure []) parseArrayString + within <- parseFieldOpt "within" v >>= maybe (pure [("","")]) (parseArray >=> concatMapM parseWithin) + pure [Right $ Classify severity n a b | (a,b) <- within, n <- ["" | null names] ++ names] parseRestrict :: RestrictType -> Val -> Parser Restrict parseRestrict restrictType v = do @@ -274,42 +290,44 @@ Just def -> do b <- parseBool def allowFields v ["default"] - return $ Restrict restrictType b [] [] [] [] Nothing + pure $ Restrict restrictType b [] [] [] [] Nothing Nothing -> do - restrictName <- parseFieldOpt "name" v >>= maybe (return []) parseArrayString - restrictWithin <- parseFieldOpt "within" v >>= maybe (return [("","")]) (parseArray >=> concatMapM parseWithin) - restrictAs <- parseFieldOpt "as" v >>= maybe (return []) parseArrayString + restrictName <- parseFieldOpt "name" v >>= maybe (pure []) parseArrayString + restrictWithin <- parseFieldOpt "within" v >>= maybe (pure [("","")]) (parseArray >=> concatMapM parseWithin) + restrictAs <- parseFieldOpt "as" v >>= maybe (pure []) parseArrayString restrictBadIdents <- parseFieldOpt "badidents" v >>= maybe (pure []) parseArrayString restrictMessage <- parseFieldOpt "message" v >>= maybeParse parseString allowFields v $ ["as" | restrictType == RestrictModule] ++ ["badidents", "name", "within", "message"] - return Restrict{restrictDefault=True,..} + pure Restrict{restrictDefault=True,..} parseWithin :: Val -> Parser [(String, String)] -- (module, decl) parseWithin v = do - x <- parseHSE parseExpWithMode v + x <- parseGHC parseExpGhcWithMode v case x of - Var _ (UnQual _ name) -> return [("",fromNamed name)] - Var _ (Qual _ (ModuleName _ mod) name) -> return [(mod, fromNamed name)] - Con _ (UnQual _ name) -> return [(fromNamed name,""),("",fromNamed name)] - Con _ (Qual _ (ModuleName _ mod) name) -> return [(mod ++ "." ++ fromNamed name,""),(mod,fromNamed name)] + L _ (HsVar _ (L _ (Unqual x))) -> pure $ f "" (occNameString x) + L _ (HsVar _ (L _ (Qual mod x))) -> pure $ f (moduleNameString mod) (occNameString x) _ -> parseFail v "Bad classification rule" + where + f mod name@(c:_) | isUpper c = [(mod,name),(mod ++ ['.' | mod /= ""] ++ name, "")] + f mod name = [(mod, name)] parseSeverityKey :: Val -> Parser (Severity, Val) parseSeverityKey v = do (s, v) <- parseObject1 v case getSeverity s of - Just sev -> return (sev, v) + Just sev -> pure (sev, v) _ -> parseFail v $ "Key should be a severity (e.g. warn/error/suggest) but got " ++ s -guessName :: Exp_ -> Exp_ -> String +guessName :: LHsExpr GhcPs -> LHsExpr GhcPs -> String guessName lhs rhs | n:_ <- rs \\ ls = "Use " ++ n | n:_ <- ls \\ rs = "Redundant " ++ n | otherwise = defaultHintName where (ls, rs) = both f (lhs, rhs) - f = filter (not . isUnifyVar) . map (\x -> fromNamed (x :: Name S)) . childrenS + f :: LHsExpr GhcPs -> [String] + f x = [y | L _ (HsVar _ (L _ x)) <- universe x, let y = occNameStr x, not $ isUnifyVar y, y /= "."] asNote :: String -> Note @@ -330,26 +348,17 @@ packages = [x | ConfigPackage x <- configs] groups = [x | ConfigGroup x <- configs] settings = concat [x | ConfigSetting x <- configs] - packageMap = Map.fromListWith (++) [(packageName, packageModules) | Package{..} <- packages] - packageMap' = Map.fromListWith (++) [(packageName, fmap unextendInstances packageGhcModules) | Package{..} <- packages] + packageMap' = Map.fromListWith (++) [(packageName, fmap unextendInstances packageModules) | Package{..} <- packages] groupMap = Map.fromListWith (\new old -> new) [(groupName, groupEnabled) | Group{..} <- groups] f Group{..} | Map.lookup groupName groupMap == Just False = [] - | otherwise = map (either (\r -> SettingMatchExp r{hintRuleScope=scope,hintRuleGhcScope=scope'}) SettingClassify) groupRules + | otherwise = map (either (\r -> SettingMatchExp r{hintRuleScope=scope'}) SettingClassify) groupRules where - scope = asScope packageMap groupImports - scope'= asScope' packageMap' (map (fmap unextendInstances) groupGhcImports) - -asScope :: Map.HashMap String [ImportDecl S] -> [Either String (ImportDecl S)] -> Scope -asScope packages xs = scopeCreate $ Module an Nothing [] (concatMap f xs) [] - where - f (Right x) = [x] - f (Left x) | Just pkg <- Map.lookup x packages = pkg - | otherwise = error $ "asScope failed to do lookup, " ++ x + scope'= asScope' packageMap' (map (fmap unextendInstances) groupImports) -asScope' :: Map.HashMap String [HsSyn.LImportDecl HsSyn.GhcPs] -> [Either String (HsSyn.LImportDecl HsSyn.GhcPs)] -> HsExtendInstances Scope' -asScope' packages xs = HsExtendInstances $ scopeCreate' (HsSyn.HsModule Nothing Nothing (concatMap f xs) [] Nothing Nothing) +asScope' :: Map.HashMap String [LImportDecl GhcPs] -> [Either String (LImportDecl GhcPs)] -> Scope +asScope' packages xs = scopeCreate (HsModule Nothing Nothing (concatMap f xs) [] Nothing Nothing) where f (Right x) = [x] f (Left x) | Just pkg <- Map.lookup x packages = pkg diff -Nru hlint-2.2.11/src/Extension.hs hlint-3.1.6/src/Extension.hs --- hlint-2.2.11/src/Extension.hs 1970-01-01 00:00:00.000000000 +0000 +++ hlint-3.1.6/src/Extension.hs 2020-06-14 18:45:05.000000000 +0000 @@ -0,0 +1,63 @@ +module Extension( + defaultExtensions, + configExtensions, + extensionImpliedEnabledBy, + extensionImplies + ) where + +import Data.List.Extra +import qualified Data.Map as Map +import GHC.LanguageExtensions.Type +import qualified Language.Haskell.GhclibParserEx.GHC.Driver.Session as GhclibParserEx + +badExtensions = + reallyBadExtensions ++ + [ Arrows -- steals proc + , UnboxedTuples, UnboxedSums -- breaks (#) lens operator + , QuasiQuotes -- breaks [x| ...], making whitespace free list comps break + , {- DoRec , -} RecursiveDo -- breaks rec + ] + +reallyBadExtensions = + [ TransformListComp -- steals the group keyword + , StaticPointers -- steals the static keyword + {- , XmlSyntax , RegularPatterns -} -- steals a-b and < operators + , AlternativeLayoutRule -- Does not play well with 'MultiWayIf' + , NegativeLiterals -- Was not enabled by HSE and enabling breaks tests. + , StarIsType -- conflicts with TypeOperators. StarIsType is currently enabled by default, + -- so adding it here has no effect except avoiding passing it to apply-refact. + -- See https://github.com/mpickering/apply-refact/issues/58 + ] + +-- | Extensions we turn on by default when parsing. Aim to parse as +-- many files as we can. +defaultExtensions :: [Extension] +defaultExtensions = enumerate \\ badExtensions + +-- | Extensions we turn on when reading config files, don't have to deal with the whole world +-- of variations - in particular, we might require spaces in some places. +configExtensions :: [Extension] +configExtensions = enumerate \\ reallyBadExtensions + +-- | This extension implies the following extensions are +-- enabled/disabled. +extensionImplies :: Extension -> ([Extension], [Extension]) +extensionImplies = \x ->Map.findWithDefault ([], []) x mp + where mp = Map.fromList extensionImplications + +-- 'x' is implied enabled by the result extensions. +extensionImpliedEnabledBy :: Extension -> [Extension] +extensionImpliedEnabledBy = \x -> Map.findWithDefault [] x mp + where + mp = Map.fromListWith (++) [(b, [a]) | (a, (bs, _)) <- extensionImplications, b <- bs] + +-- 'x' is implied disabled by the result extensions. Not called at this time. +_extensionImpliedDisabledBy :: Extension -> [Extension] +_extensionImpliedDisabledBy = \x -> Map.findWithDefault [] x mp + where + mp = Map.fromListWith (++) [(b, [a]) | (a, (_, bs)) <- extensionImplications, b <- bs] + +-- | (a, bs) means extension a implies all of bs. Uses GHC source at +-- DynFlags.impliedXFlags +extensionImplications :: [(Extension, ([Extension], [Extension]))] +extensionImplications = GhclibParserEx.extensionImplications diff -Nru hlint-2.2.11/src/Fixity.hs hlint-3.1.6/src/Fixity.hs --- hlint-2.2.11/src/Fixity.hs 1970-01-01 00:00:00.000000000 +0000 +++ hlint-3.1.6/src/Fixity.hs 2020-05-21 15:48:31.000000000 +0000 @@ -0,0 +1,106 @@ +{-# LANGUAGE ViewPatterns #-} + +module Fixity( + FixityInfo, Associativity(..), + defaultFixities, + fromFixitySig, toFixitySig, toFixity, + ) where + +import GHC.Generics(Associativity(..)) +import GHC.Hs.Binds +import GHC.Hs.Extension +import OccName +import RdrName +import SrcLoc +import BasicTypes +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader +import Language.Haskell.GhclibParserEx.Fixity + +-- Lots of things define a fixity. None define it quite right, so let's have our own type. + +-- | A Fixity definition, comprising the name the fixity applies to, +-- the direction and the precedence. As an example, a source file containing: +-- +-- > infixr 3 `foo` +-- +-- would create @(\"foo\", RightAssociative, 3)@. +type FixityInfo = (String, Associativity, Int) + +fromFixitySig :: FixitySig GhcPs -> [FixityInfo] +fromFixitySig (FixitySig _ names (Fixity _ i dir)) = + [(rdrNameStr name, f dir, i) | name <- names] + where + f InfixL = LeftAssociative + f InfixR = RightAssociative + f InfixN = NotAssociative +fromFixitySig _ = [] + +toFixity :: FixityInfo -> (String, Fixity) +toFixity (name, dir, i) = (name, Fixity NoSourceText i $ f dir) + where + f LeftAssociative = InfixL + f RightAssociative = InfixR + f NotAssociative = InfixN + +fromFixity :: (String, Fixity) -> FixityInfo +fromFixity (name, Fixity _ i dir) = (name, assoc dir, i) + where + assoc dir = case dir of + InfixL -> LeftAssociative + InfixR -> RightAssociative + InfixN -> NotAssociative + +toFixitySig :: FixityInfo -> FixitySig GhcPs +toFixitySig (toFixity -> (name, x)) = FixitySig noExtField [noLoc $ mkRdrUnqual (mkVarOcc name)] x + +defaultFixities :: [FixityInfo] +defaultFixities = map fromFixity $ customFixities ++ baseFixities ++ lensFixities ++ otherFixities + +-- List as provided at https://github.com/ndmitchell/hlint/issues/416. +lensFixities :: [(String, Fixity)] +lensFixities = concat + [ infixr_ 4 ["%%@~","<%@~","%%~","<+~","<*~","<-~","","??"] + , infixl_ 8 ["^.","^@."] + , infixr_ 9 ["<.>","<.",".>"] + , infixr_ 4 ["%@~",".~","+~","*~","-~","//~","^~","^^~","**~","&&~","<>~","||~","%~"] + , infix_ 4 ["%@=",".=","+=","*=","-=","//=","^=","^^=","**=","&&=","<>=","||=","%="] + , infixr_ 2 ["<~"] + , infixr_ 2 ["`zoom`","`magnify`"] + , infixl_ 8 ["^..","^?","^?!","^@..","^@?","^@?!"] + , infixl_ 8 ["^#"] + , infixr_ 4 ["<#~","#~","#%~","<#%~","#%%~"] + , infix_ 4 ["<#=","#=","#%=","<#%=","#%%="] + , infixl_ 9 [":>"] + , infixr_ 4 ["~","<~","<.>~","<<.>~"] + , infix_ 4 ["=","<=","<.>=","<<.>="] + , infixr_ 4 [".|.~",".&.~","<.|.~","<.&.~"] + , infix_ 4 [".|.=",".&.=","<.|.=","<.&.="] + ] + +otherFixities :: [(String, Fixity)] +otherFixities = concat + -- hspec + [ infix_ 1 ["shouldBe","shouldSatisfy","shouldStartWith","shouldEndWith","shouldContain","shouldMatchList" + ,"shouldReturn","shouldNotBe","shouldNotSatisfy","shouldNotContain","shouldNotReturn","shouldThrow"] + -- quickcheck + , infixr_ 0 ["==>"] + , infix_ 4 ["==="] + -- esqueleto + , infix_ 4 ["==."] + -- lattices + , infixr_ 5 ["\\/"] -- \/ + , infixr_ 6 ["/\\"] -- /\ + ] + +customFixities :: [(String, Fixity)] +customFixities = + infixl_ 1 ["`on`"] + -- See https://github.com/ndmitchell/hlint/issues/425 + -- otherwise GTK apps using `on` at a different fixity have + -- spurious warnings. diff -Nru hlint-2.2.11/src/GHC/All.hs hlint-3.1.6/src/GHC/All.hs --- hlint-2.2.11/src/GHC/All.hs 1970-01-01 00:00:00.000000000 +0000 +++ hlint-3.1.6/src/GHC/All.hs 2020-06-14 18:45:05.000000000 +0000 @@ -0,0 +1,205 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ViewPatterns #-} + +module GHC.All( + CppFlags(..), ParseFlags(..), defaultParseFlags, + parseFlagsAddFixities, parseFlagsSetLanguage, + ParseError(..), ModuleEx(..), + parseModuleEx, createModuleEx, ghcComments, + parseExpGhcWithMode, parseImportDeclGhcWithMode, parseDeclGhcWithMode, + ) where + +import Util +import Data.Char +import Data.List.Extra +import Timing +import Language.Preprocessor.Cpphs +import qualified Data.Map as Map +import System.IO.Extra +import Fixity +import Extension +import FastString + +import GHC.Hs +import SrcLoc +import ErrUtils +import Outputable +import Lexer hiding (context) +import GHC.LanguageExtensions.Type +import ApiAnnotation +import DynFlags hiding (extensions) +import Bag + +import Language.Haskell.GhclibParserEx.GHC.Parser +import Language.Haskell.GhclibParserEx.Fixity +import GHC.Util + +-- | What C pre processor should be used. +data CppFlags + = NoCpp -- ^ No pre processing is done. + | CppSimple -- ^ Lines prefixed with @#@ are stripped. + | Cpphs CpphsOptions -- ^ The @cpphs@ library is used. + +-- | Created with 'defaultParseFlags', used by 'parseModuleEx'. +data ParseFlags = ParseFlags + {cppFlags :: CppFlags -- ^ How the file is preprocessed (defaults to 'NoCpp'). + ,baseLanguage :: Maybe Language -- ^ Base language (e.g. Haskell98, Haskell2010), defaults to 'Nothing'. + ,enabledExtensions :: [Extension] -- ^ List of extensions enabled for parsing, defaults to many non-conflicting extensions. + ,disabledExtensions :: [Extension] -- ^ List of extensions disabled for parsing, usually empty. + ,fixities :: [FixityInfo] -- ^ List of fixities to be aware of, defaults to those defined in @base@. + } + +-- | Default value for 'ParseFlags'. +defaultParseFlags :: ParseFlags +defaultParseFlags = ParseFlags NoCpp Nothing defaultExtensions [] defaultFixities + +-- | Given some fixities, add them to the existing fixities in 'ParseFlags'. +parseFlagsAddFixities :: [FixityInfo] -> ParseFlags -> ParseFlags +parseFlagsAddFixities fx x = x{fixities = fx ++ fixities x} + +parseFlagsSetLanguage :: (Maybe Language, ([Extension], [Extension])) -> ParseFlags -> ParseFlags +parseFlagsSetLanguage (l, (es, ds)) x = x{baseLanguage = l, enabledExtensions = es, disabledExtensions = ds} + + +runCpp :: CppFlags -> FilePath -> String -> IO String +runCpp NoCpp _ x = pure x +runCpp CppSimple _ x = pure $ unlines [if "#" `isPrefixOf` trimStart x then "" else x | x <- lines x] +runCpp (Cpphs o) file x = dropLine <$> runCpphs o file x + where + -- LINE pragmas always inserted when locations=True + dropLine (line1 -> (a,b)) | "{-# LINE " `isPrefixOf` a = b + dropLine x = x + +--------------------------------------------------------------------- +-- PARSING + +-- | A parse error. +data ParseError = ParseError + { parseErrorLocation :: SrcSpan -- ^ Location of the error. + , parseErrorMessage :: String -- ^ Message about the cause of the error. + , parseErrorContents :: String -- ^ Snippet of several lines (typically 5) including a @>@ character pointing at the faulty line. + } + +-- | Result of 'parseModuleEx', representing a parsed module. +data ModuleEx = ModuleEx { + ghcModule :: Located (HsModule GhcPs) + , ghcAnnotations :: ApiAnns +} + +-- | Extract a list of all of a parsed module's comments. +ghcComments :: ModuleEx -> [Located AnnotationComment] +ghcComments m = concat (Map.elems $ snd (ghcAnnotations m)) + + +-- | The error handler invoked when GHC parsing has failed. +ghcFailOpParseModuleEx :: String + -> FilePath + -> String + -> (SrcSpan, ErrUtils.MsgDoc) + -> IO (Either ParseError ModuleEx) +ghcFailOpParseModuleEx ppstr file str (loc, err) = do + let pe = case loc of + RealSrcSpan r -> context (srcSpanStartLine r) ppstr + _ -> "" + msg = Outputable.showSDoc baseDynFlags err + pure $ Left $ ParseError loc msg pe + +-- GHC extensions to enable/disable given HSE parse flags. +ghcExtensionsFromParseFlags :: ParseFlags -> ([Extension], [Extension]) +ghcExtensionsFromParseFlags ParseFlags{enabledExtensions=es, disabledExtensions=ds}= (es, ds) + +-- GHC fixities given HSE parse flags. +ghcFixitiesFromParseFlags :: ParseFlags -> [(String, Fixity)] +ghcFixitiesFromParseFlags = map toFixity . fixities + +-- These next two functions get called frorm 'Config/Yaml.hs' for user +-- defined hint rules. + +parseModeToFlags :: ParseFlags -> DynFlags +parseModeToFlags parseMode = + flip lang_set (baseLanguage parseMode) $ foldl' xopt_unset (foldl' xopt_set baseDynFlags enable) disable + where + (enable, disable) = ghcExtensionsFromParseFlags parseMode + +parseExpGhcWithMode :: ParseFlags -> String -> ParseResult (LHsExpr GhcPs) +parseExpGhcWithMode parseMode s = + let fixities = ghcFixitiesFromParseFlags parseMode + in case parseExpression s $ parseModeToFlags parseMode of + POk pst a -> POk pst $ applyFixities fixities a + f@PFailed{} -> f + +parseImportDeclGhcWithMode :: ParseFlags -> String -> ParseResult (LImportDecl GhcPs) +parseImportDeclGhcWithMode parseMode s = + parseImport s $ parseModeToFlags parseMode + +parseDeclGhcWithMode :: ParseFlags -> String -> ParseResult (LHsDecl GhcPs) +parseDeclGhcWithMode parseMode s = + let fixities = ghcFixitiesFromParseFlags parseMode + in case parseDeclaration s $ parseModeToFlags parseMode of + POk pst a -> POk pst $ applyFixities fixities a + f@PFailed{} -> f + +-- | Create a 'ModuleEx' from GHC annotations and module tree. It +-- is assumed the incoming parse module has not been adjusted to +-- account for operator fixities (it uses the HLint default fixities). +createModuleEx :: ApiAnns -> Located (HsModule GhcPs) -> ModuleEx +createModuleEx anns ast = + ModuleEx (applyFixities (fixitiesFromModule ast ++ map toFixity defaultFixities) ast) anns + +-- | Parse a Haskell module. Applies the C pre processor, and uses +-- best-guess fixity resolution if there are ambiguities. The +-- filename @-@ is treated as @stdin@. Requires some flags (often +-- 'defaultParseFlags'), the filename, and optionally the contents of +-- that file. +-- +-- Note that certain programs, e.g. @main = do@ successfully parse with GHC, but then +-- fail with an error in the renamer. These programs will return a successful parse. +parseModuleEx :: ParseFlags -> FilePath -> Maybe String -> IO (Either ParseError ModuleEx) +parseModuleEx flags file str = timedIO "Parse" file $ do + str <- case str of + Just x -> pure x + Nothing | file == "-" -> getContentsUTF8 + | otherwise -> readFileUTF8' file + str <- pure $ dropPrefix "\65279" str -- remove the BOM if it exists, see #130 + ppstr <- runCpp (cppFlags flags) file str + let enableDisableExts = ghcExtensionsFromParseFlags flags + dynFlags <- parsePragmasIntoDynFlags baseDynFlags enableDisableExts file ppstr + case dynFlags of + Right ghcFlags -> do + ghcFlags <- pure $ lang_set ghcFlags $ baseLanguage flags + case fileToModule file ppstr ghcFlags of + POk s a -> do + let errs = bagToList . snd $ getMessages s ghcFlags + if not $ null errs then + handleParseFailure ghcFlags ppstr file str errs + else do + let anns = + ( Map.fromListWith (++) $ annotations s + , Map.fromList ((noSrcSpan, comment_q s) : annotations_comments s) + ) + let fixes = fixitiesFromModule a ++ ghcFixitiesFromParseFlags flags + pure $ Right (ModuleEx (applyFixities fixes a) anns) + PFailed s -> + handleParseFailure ghcFlags ppstr file str $ bagToList . snd $ getMessages s ghcFlags + Left msg -> do + -- Parsing GHC flags from dynamic pragmas in the source + -- has failed. When this happens, it's reported by + -- exception. It's impossible or at least fiddly getting a + -- location so we skip that for now. Synthesize a parse + -- error. + let loc = mkSrcLoc (mkFastString file) (1 :: Int) (1 :: Int) + pure $ Left (ParseError (mkSrcSpan loc loc) msg ppstr) + where + handleParseFailure ghcFlags ppstr file str errs = + let errMsg = head errs + loc = errMsgSpan errMsg + doc = formatErrDoc ghcFlags (errMsgDoc errMsg) + in ghcFailOpParseModuleEx ppstr file str (loc, doc) + + +-- | Given a line number, and some source code, put bird ticks around the appropriate bit. +context :: Int -> String -> String +context lineNo src = + unlines $ dropWhileEnd (all isSpace) $ dropWhile (all isSpace) $ + zipWith (++) ticks $ take 5 $ drop (lineNo - 3) $ lines src ++ ["","","","",""] + where ticks = drop (3 - lineNo) [" "," ","> "," "," "] diff -Nru hlint-2.2.11/src/GHC/Util/ApiAnnotation.hs hlint-3.1.6/src/GHC/Util/ApiAnnotation.hs --- hlint-2.2.11/src/GHC/Util/ApiAnnotation.hs 2019-09-24 17:55:46.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/ApiAnnotation.hs 2020-05-03 12:52:17.000000000 +0000 @@ -1,8 +1,8 @@ module GHC.Util.ApiAnnotation ( comment, commentText, isCommentMultiline - , pragmas, flags, langExts - , mkFlags, mkLangExts + , pragmas, flags, languagePragmas + , mkFlags, mkLanguagePragmas ) where import ApiAnnotation @@ -83,12 +83,12 @@ <|> stripPrefixCI "OPTIONS " s] , let opts = words rest] --- Language extensions. The first element of the pair is the (located) --- annotation comment that enables the extensions enumerated by he --- second element of the pair. -langExts :: [(Located AnnotationComment, String)] +-- Language pragmas. The first element of the +-- pair is the (located) annotation comment that enables the +-- pragmas enumerated by he second element of the pair. +languagePragmas :: [(Located AnnotationComment, String)] -> [(Located AnnotationComment, [String])] -langExts ps = +languagePragmas ps = [(c, exts) | (c, s) <- ps , Just rest <- [stripPrefixCI "LANGUAGE " s] , let exts = map trim (splitOn "," rest)] @@ -96,8 +96,8 @@ -- Given a list of flags, make a GHC options pragma. mkFlags :: SrcSpan -> [String] -> Located AnnotationComment mkFlags loc flags = - LL loc $ AnnBlockComment ("{-# " ++ "OPTIONS_GHC " ++ unwords flags ++ " #-}") + L loc $ AnnBlockComment ("{-# " ++ "OPTIONS_GHC " ++ unwords flags ++ " #-}") -mkLangExts :: SrcSpan -> [String] -> Located AnnotationComment -mkLangExts loc exts = - LL loc $ AnnBlockComment ("{-# " ++ "LANGUAGE " ++ intercalate ", " exts ++ " #-}") +mkLanguagePragmas :: SrcSpan -> [String] -> Located AnnotationComment +mkLanguagePragmas loc exts = + L loc $ AnnBlockComment ("{-# " ++ "LANGUAGE " ++ intercalate ", " exts ++ " #-}") diff -Nru hlint-2.2.11/src/GHC/Util/Brackets.hs hlint-3.1.6/src/GHC/Util/Brackets.hs --- hlint-2.2.11/src/GHC/Util/Brackets.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/Brackets.hs 2020-05-13 11:33:34.000000000 +0000 @@ -1,35 +1,37 @@ {-# LANGUAGE MultiParamTypeClasses , FlexibleInstances, FlexibleContexts #-} -module GHC.Util.Brackets (Brackets'(..), isApp,isOpApp,isAnyApp) where +{-# OPTIONS_GHC -Wno-incomplete-patterns -Wno-overlapping-patterns #-} -import HsSyn +module GHC.Util.Brackets (Brackets(..), isApp,isOpApp,isAnyApp) where + +import GHC.Hs import SrcLoc import BasicTypes import Language.Haskell.GhclibParserEx.GHC.Hs.Expr -class Brackets' a where - remParen' :: a -> Maybe a -- Remove one paren or nothing if there is no paren. - addParen' :: a -> a -- Write out a paren. +class Brackets a where + remParen :: a -> Maybe a -- Remove one paren or nothing if there is no paren. + addParen :: a -> a -- Write out a paren. -- | Is this item lexically requiring no bracketing ever i.e. is -- totally atomic. - isAtom' :: a -> Bool + isAtom :: a -> Bool -- | Is the child safe free from brackets in the parent -- position. Err on the side of caution, True = don't know. - needBracket' :: Int -> a -> a -> Bool + needBracket :: Int -> a -> a -> Bool -instance Brackets' (LHsExpr GhcPs) where +instance Brackets (LHsExpr GhcPs) where -- When GHC parses a section in concrete syntax, it will produce an -- 'HsPar (Section[L|R])'. There is no concrete syntax that will -- result in a "naked" section. Consequently, given an expression, - -- when stripping brackets (c.f. 'Hint.Brackets'), don't remove the + -- when stripping brackets (c.f. 'Hint.Brackets), don't remove the -- paren's surrounding a section - they are required. - remParen' (LL _ (HsPar _ (LL _ SectionL{}))) = Nothing - remParen' (LL _ (HsPar _ (LL _ SectionR{}))) = Nothing - remParen' (LL _ (HsPar _ x)) = Just x - remParen' _ = Nothing + remParen (L _ (HsPar _ (L _ SectionL{}))) = Nothing + remParen (L _ (HsPar _ (L _ SectionR{}))) = Nothing + remParen (L _ (HsPar _ x)) = Just x + remParen _ = Nothing - addParen' e = noLoc $ HsPar noExt e + addParen e = noLoc $ HsPar noExtField e - isAtom' (LL _ x) = case x of + isAtom (L _ x) = case x of HsVar{} -> True HsUnboundVar{} -> True HsRecFld{} -> True @@ -60,33 +62,46 @@ isNegativeOverLit OverLit {ol_val=HsIntegral i} = il_neg i isNegativeOverLit OverLit {ol_val=HsFractional f} = fl_neg f isNegativeOverLit _ = False - isAtom' _ = False -- '{-# COMPLETE LL #-}' + isAtom _ = False -- '{-# COMPLETE L #-}' + + needBracket i parent child -- Note: i is the index in children, not in the AST. + | isAtom child = False + | isSection parent, L _ HsApp{} <- child = False + | L _ OpApp{} <- parent, L _ HsApp{} <- child, i /= 0 || isAtomOrApp child = False + | L _ ExplicitList{} <- parent = False + | L _ ExplicitTuple{} <- parent = False + | L _ HsIf{} <- parent, isAnyApp child = False + | L _ HsApp{} <- parent, i == 0, L _ HsApp{} <- child = False + | L _ ExprWithTySig{} <- parent, i == 0, isApp child = False + | L _ RecordCon{} <- parent = False + | L _ RecordUpd{} <- parent, i /= 0 = False + + -- These all have view patterns embedded within them, or are naturally followed by ->, so we have to watch out for + -- @(x::y) -> z@ which is valid, as either a type annotation, or a view pattern. + | L _ HsLet{} <- parent, isApp child = False + | L _ HsDo{} <- parent, isAnyApp child = False + | L _ HsLam{} <- parent, isAnyApp child = False + | L _ HsCase{} <- parent, isAnyApp child = False - needBracket' i parent child -- Note: i is the index in children, not in the AST. - | isAtom' child = False - | isSection parent, LL _ HsApp{} <- child = False - | LL _ OpApp{} <- parent, LL _ HsApp{} <- child = False - | LL _ HsLet{} <- parent, LL _ HsApp{} <- child = False - | LL _ HsDo{} <- parent = False - | LL _ ExplicitList{} <- parent = False - | LL _ ExplicitTuple{} <- parent = False - | LL _ HsIf{} <- parent, isAnyApp child = False - | LL _ HsApp{} <- parent, i == 0, LL _ HsApp{} <- child = False - | LL _ ExprWithTySig{} <- parent, i == 0, isApp child = False - | LL _ RecordCon{} <- parent = False - | LL _ RecordUpd{} <- parent, i /= 0 = False - | LL _ HsCase{} <- parent, i /= 0 || isAnyApp child = False - | LL _ HsLam{} <- parent = False -- might be either the RHS of a PViewPat, or the lambda body (neither needs brackets) - | LL _ HsPar{} <- parent = False - | LL _ HsDo {} <- parent = False + | L _ HsPar{} <- parent = False | otherwise = True -instance Brackets' (Pat GhcPs) where - remParen' (LL _ (ParPat _ x)) = Just x - remParen' _ = Nothing - addParen' e = noLoc $ ParPat noExt e +-- | Am I an HsApp such that having me in an infix doesn't require brackets. +-- Before BlockArguments that was _all_ HsApps. Now, imagine: +-- +-- (f \x -> x) *> ... +-- (f do x) *> ... +isAtomOrApp :: LHsExpr GhcPs -> Bool +isAtomOrApp x | isAtom x = True +isAtomOrApp (L _ (HsApp _ _ x)) = isAtomOrApp x +isAtomOrApp _ = False + +instance Brackets (Located (Pat GhcPs)) where + remParen (L _ (ParPat _ x)) = Just x + remParen _ = Nothing + addParen e = noLoc $ ParPat noExtField e - isAtom' (LL _ x) = case x of + isAtom (L _ x) = case x of ParPat{} -> True TuplePat{} -> True ListPat{} -> True @@ -108,20 +123,20 @@ isSignedLit HsFloatPrim{} = True isSignedLit HsDoublePrim{} = True isSignedLit _ = False - isAtom' _ = False -- '{-# COMPLETE LL #-}' + isAtom _ = False -- '{-# COMPLETE L #-}' - needBracket' _ parent child - | isAtom' child = False - | LL _ TuplePat{} <- parent = False - | LL _ ListPat{} <- parent = False + needBracket _ parent child + | isAtom child = False + | L _ TuplePat{} <- parent = False + | L _ ListPat{} <- parent = False | otherwise = True -instance Brackets' (LHsType GhcPs) where - remParen' (LL _ (HsParTy _ x)) = Just x - remParen' _ = Nothing - addParen' e = noLoc $ HsParTy noExt e +instance Brackets (LHsType GhcPs) where + remParen (L _ (HsParTy _ x)) = Just x + remParen _ = Nothing + addParen e = noLoc $ HsParTy noExtField e - isAtom' (LL _ x) = case x of + isAtom (L _ x) = case x of HsParTy{} -> True HsTupleTy{} -> True HsListTy{} -> True @@ -132,18 +147,18 @@ HsSpliceTy{} -> True HsWildCardTy{} -> True _ -> False - isAtom' _ = False -- '{-# COMPLETE LL #-}' + isAtom _ = False -- '{-# COMPLETE L #-}' - needBracket' _ parent child - | isAtom' child = False + needBracket _ parent child + | isAtom child = False -- a -> (b -> c) is not a required bracket, but useful for documentation about arity etc. -- | TyFun{} <- parent, i == 1, TyFun{} <- child = False - | LL _ HsFunTy{} <- parent, LL _ HsAppTy{} <- child = False - | LL _ HsTupleTy{} <- parent = False - | LL _ HsListTy{} <- parent = False - | LL _ HsExplicitTupleTy{} <- parent = False - | LL _ HsListTy{} <- parent = False - | LL _ HsExplicitListTy{} <- parent = False - | LL _ HsOpTy{} <- parent, LL _ HsAppTy{} <- child = False - | LL _ HsParTy{} <- parent = False + | L _ HsFunTy{} <- parent, L _ HsAppTy{} <- child = False + | L _ HsTupleTy{} <- parent = False + | L _ HsListTy{} <- parent = False + | L _ HsExplicitTupleTy{} <- parent = False + | L _ HsListTy{} <- parent = False + | L _ HsExplicitListTy{} <- parent = False + | L _ HsOpTy{} <- parent, L _ HsAppTy{} <- child = False + | L _ HsParTy{} <- parent = False | otherwise = True diff -Nru hlint-2.2.11/src/GHC/Util/DynFlags.hs hlint-3.1.6/src/GHC/Util/DynFlags.hs --- hlint-2.2.11/src/GHC/Util/DynFlags.hs 2020-01-27 16:04:14.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/DynFlags.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,9 +1,9 @@ -module GHC.Util.DynFlags (baseDynFlags) where +module GHC.Util.DynFlags (initGlobalDynFlags, baseDynFlags) where import DynFlags import GHC.LanguageExtensions.Type import Data.List.Extra -import Language.Haskell.GhclibParserEx.Config +import Language.Haskell.GhclibParserEx.GHC.Settings.Config baseDynFlags :: DynFlags baseDynFlags = @@ -17,3 +17,7 @@ -- hlint.yaml:860 let enable = [TemplateHaskellQuotes] in foldl' xopt_set (defaultDynFlags fakeSettings fakeLlvmConfig) enable + + +initGlobalDynFlags :: IO () +initGlobalDynFlags = setUnsafeGlobalDynFlags baseDynFlags diff -Nru hlint-2.2.11/src/GHC/Util/FreeVars.hs hlint-3.1.6/src/GHC/Util/FreeVars.hs --- hlint-2.2.11/src/GHC/Util/FreeVars.hs 2020-02-07 10:36:33.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/FreeVars.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,26 +1,24 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ViewPatterns #-} module GHC.Util.FreeVars ( - vars', varss', pvars', freeVarSet' - , Vars' (..), FreeVars'(..) , AllVars' (..) + vars, varss, pvars, + Vars (..), FreeVars(..) , AllVars (..) ) where import RdrName -import HsTypes +import GHC.Hs.Types import OccName import Name -import HsSyn +import GHC.Hs import SrcLoc import Bag (bagToList) -import Data.Generics.Uniplate.Data () -import Data.Generics.Uniplate.Operations +import Data.Generics.Uniplate.DataOnly import Data.Monoid import Data.Semigroup +import Data.List.Extra import Data.Set (Set) import qualified Data.Set as Set import Prelude @@ -31,238 +29,239 @@ ( ^- ) = Set.difference -- See [Note : Space leaks lurking here?] below. -data Vars' = Vars'{bound' :: Set OccName, free' :: Set OccName} +data Vars = Vars{bound :: Set OccName, free :: Set OccName} -- Useful for debugging. -instance Show Vars' where - show (Vars' bs fs) = "bound : " ++ +instance Show Vars where + show (Vars bs fs) = "bound : " ++ show (map occNameString (Set.toList bs)) ++ ", free : " ++ show (map occNameString (Set.toList fs)) -instance Semigroup Vars' where - Vars' x1 x2 <> Vars' y1 y2 = Vars' (x1 ^+ y1) (x2 ^+ y2) +instance Semigroup Vars where + Vars x1 x2 <> Vars y1 y2 = Vars (x1 ^+ y1) (x2 ^+ y2) -instance Monoid Vars' where - mempty = Vars' Set.empty Set.empty - mconcat vs = Vars' (Set.unions $ map bound' vs) (Set.unions $ map free' vs) +instance Monoid Vars where + mempty = Vars Set.empty Set.empty + mconcat vs = Vars (Set.unions $ map bound vs) (Set.unions $ map free vs) --- A type `a` is a model of `AllVars' a` if exists a function --- `allVars'` for producing a pair of the bound and free varaiable +-- A type `a` is a model of `AllVars a` if exists a function +-- `allVars` for producing a pair of the bound and free varaiable -- sets in a value of `a`. -class AllVars' a where +class AllVars a where -- | Return the variables, erring on the side of more free -- variables. - allVars' :: a -> Vars' + allVars :: a -> Vars --- A type `a` is a model of `FreeVars' a` if exists a function --- `freeVars'` for producing a set of free varaiable of a value of +-- A type `a` is a model of `FreeVars a` if exists a function +-- `freeVars` for producing a set of free varaiable of a value of -- `a`. -class FreeVars' a where +class FreeVars a where -- | Return the variables, erring on the side of more free -- variables. - freeVars' :: a -> Set OccName + freeVars :: a -> Set OccName -- Trivial instances. -instance AllVars' Vars' where allVars' = id -instance FreeVars' (Set OccName) where freeVars' = id +instance AllVars Vars where allVars = id +instance FreeVars (Set OccName) where freeVars = id -- [Note : Space leaks lurking here?] -- ================================== -- We make use of `foldr`. @cocreature suggests we want bangs on `data --- Vars` and replace usages of `mconcat` with `foldl'`. -instance (AllVars' a) => AllVars' [a] where allVars' = mconcat . map allVars' -instance (FreeVars' a) => FreeVars' [a] where freeVars' = Set.unions . map freeVars' +-- Vars` and replace usages of `mconcat` with `foldl`. +instance (AllVars a) => AllVars [a] where allVars = mconcatMap allVars +instance (FreeVars a) => FreeVars [a] where freeVars = Set.unions . map freeVars -- Construct a `Vars` value with no bound vars. -freeVars_' :: (FreeVars' a) => a -> Vars' -freeVars_' = Vars' Set.empty . freeVars' +freeVars_ :: (FreeVars a) => a -> Vars +freeVars_ = Vars Set.empty . freeVars --- `inFree' a b` is the set of free variables in 'a' together with the --- free variables in 'b' not bound in 'a'. -inFree' :: (AllVars' a, FreeVars' b) => a -> b -> Set OccName -inFree' a b = free' aa ^+ (freeVars' b ^- bound' aa) - where aa = allVars' a - --- `inVars' a b` is a value of `Vars_'` with bound variables the union --- of the bound variables of 'a' and 'b' and free variables the union --- of the free variables of 'a' and the free variables of 'b' not --- bound by 'a'. -inVars' :: (AllVars' a, AllVars' b) => a -> b -> Vars' -inVars' a b = - Vars' (bound' aa ^+ bound' bb) (free' aa ^+ (free' bb ^- bound' aa)) - where aa = allVars' a - bb = allVars' b +-- `inFree a b` is the set of free variables in a together with the +-- free variables in b not bound in a. +inFree :: (AllVars a, FreeVars b) => a -> b -> Set OccName +inFree a b = free aa ^+ (freeVars b ^- bound aa) + where aa = allVars a + +-- `inVars a b` is a value of `Vars_` with bound variables the union +-- of the bound variables of a and b and free variables the union +-- of the free variables of a and the free variables of b not +-- bound by a. +inVars :: (AllVars a, AllVars b) => a -> b -> Vars +inVars a b = + Vars (bound aa ^+ bound bb) (free aa ^+ (free bb ^- bound aa)) + where aa = allVars a + bb = allVars b -- Get an `OccName` out of a reader name. -unqualNames' :: Located RdrName -> [OccName] -unqualNames' (dL -> L _ (Unqual x)) = [x] -unqualNames' (dL -> L _ (Exact x)) = [nameOccName x] -unqualNames' _ = [] - -instance FreeVars' (LHsExpr GhcPs) where - freeVars' (dL -> L _ (HsVar _ x)) = Set.fromList $ unqualNames' x -- Variable. - freeVars' (dL -> L _ (HsUnboundVar _ x)) = Set.fromList [unboundVarOcc x] -- Unbound variable; also used for "holes". - freeVars' (dL -> L _ (HsLam _ mg)) = free' (allVars' mg) -- Lambda abstraction. Currently always a single match. - freeVars' (dL -> L _ (HsLamCase _ mg)) = free' (allVars' mg) -- Lambda-case. - freeVars' (dL -> L _ (HsCase _ of_ MG{mg_alts=(dL -> L _ ms)})) = freeVars' of_ ^+ free' (allVars' ms) -- Case expr. - freeVars' (dL -> L _ (HsLet _ binds e)) = inFree' binds e -- Let (rec). - freeVars' (dL -> L _ (HsDo _ ctxt (dL -> L _ stmts))) = free' (allVars' stmts) -- Do block. - freeVars' (dL -> L _ (RecordCon _ _ (HsRecFields flds _))) = Set.unions $ map freeVars' flds -- Record construction. - freeVars' (dL -> L _ (RecordUpd _ e flds)) = Set.unions $ freeVars' e : map freeVars' flds -- Record update. - freeVars' (dL -> L _ (HsMultiIf _ grhss)) = free' (allVars' grhss) -- Multi-way if. - - freeVars' (dL -> L _ HsConLikeOut{}) = mempty -- After typechecker. - freeVars' (dL -> L _ HsRecFld{}) = mempty -- Variable pointing to a record selector. - freeVars' (dL -> L _ HsOverLabel{}) = mempty -- Overloaded label. The id of the in-scope 'fromLabel'. - freeVars' (dL -> L _ HsIPVar{}) = mempty -- Implicit parameter. - freeVars' (dL -> L _ HsOverLit{}) = mempty -- Overloaded literal. - freeVars' (dL -> L _ HsLit{}) = mempty -- Simple literal. - freeVars' (dL -> L _ HsRnBracketOut{}) = mempty -- Renamer produces these. - freeVars' (dL -> L _ HsTcBracketOut{}) = mempty -- Typechecker produces these. - freeVars' (dL -> L _ HsWrap{}) = mempty -- Typechecker output. - - -- freeVars' (dL -> e@(L _ HsAppType{})) = freeVars' $ children e -- Visible type application e.g. 'f @ Int x y'. - -- freeVars' (dL -> e@(L _ HsApp{})) = freeVars' $ children e -- Application. - -- freeVars' (dL -> e@(L _ OpApp{})) = freeVars' $ children e -- Operator application. - -- freeVars' (dL -> e@(L _ NegApp{})) = freeVars' $ children e -- Negation operator. - -- freeVars' (dL -> e@(L _ HsPar{})) = freeVars' $ children e -- Parenthesized expr. - -- freeVars' (dL -> e@(L _ SectionL{})) = freeVars' $ children e -- Left section. - -- freeVars' (dL -> e@(L _ SectionR{})) = freeVars' $ children e -- Right section. - -- freeVars' (dL -> e@(L _ ExplicitTuple{})) = freeVars' $ children e -- Explicit tuple and sections thereof. - -- freeVars' (dL -> e@(L _ ExplicitSum{})) = freeVars' $ children e -- Used for unboxed sum types. - -- freeVars' (dL -> e@(L _ HsIf{})) = freeVars' $ children e -- If. - -- freeVars' (dL -> e@(L _ ExplicitList{})) = freeVars' $ children e -- Syntactic list e.g. '[a, b, c]'. - -- freeVars' (dL -> e@(L _ ExprWithTySig{})) = freeVars' $ children e -- Expr with type signature. - -- freeVars' (dL -> e@(L _ ArithSeq {})) = freeVars' $ children e -- Arithmetic sequence. - -- freeVars' (dL -> e@(L _ HsSCC{})) = freeVars' $ children e -- Set cost center pragma (expr whose const is to be measured). - -- freeVars' (dL -> e@(L _ HsCoreAnn{})) = freeVars' $ children e -- Pragma. - -- freeVars' (dL -> e@(L _ HsBracket{})) = freeVars' $ children e -- Haskell bracket. - -- freeVars' (dL -> e@(L _ HsSpliceE{})) = freeVars' $ children e -- Template haskell splice expr. - -- freeVars' (dL -> e@(L _ HsProc{})) = freeVars' $ children e -- Proc notation for arrows. - -- freeVars' (dL -> e@(L _ HsStatic{})) = freeVars' $ children e -- Static pointers extension. - -- freeVars' (dL -> e@(L _ HsArrApp{})) = freeVars' $ children e -- Arrow tail or arrow application. - -- freeVars' (dL -> e@(L _ HsArrForm{})) = freeVars' $ children e -- Come back to it. Arrow tail or arrow application. - -- freeVars' (dL -> e@(L _ HsTick{})) = freeVars' $ children e -- Haskell program coverage (Hpc) support. - -- freeVars' (dL -> e@(L _ HsBinTick{})) = freeVars' $ children e -- Haskell program coverage (Hpc) support. - -- freeVars' (dL -> e@(L _ HsTickPragma{})) = freeVars' $ children e -- Haskell program coverage (Hpc) support. - -- freeVars' (dL -> e@(L _ EAsPat{})) = freeVars' $ children e -- Expr as pat. - -- freeVars' (dL -> e@(L _ EViewPat{})) = freeVars' $ children e -- View pattern. - -- freeVars' (dL -> e@(L _ ELazyPat{})) = freeVars' $ children e -- Lazy pattern. - - freeVars' e = freeVars' $ children e - -instance FreeVars' (LHsRecField GhcPs (LHsExpr GhcPs)) where - freeVars' (dL -> L _ (HsRecField _ x _)) = freeVars' x - -instance FreeVars' (LHsRecUpdField GhcPs) where - freeVars' (dL -> L _ (HsRecField _ x _)) = freeVars' x - -instance AllVars' (LPat GhcPs) where - allVars' (VarPat _ (dL -> L _ x)) = Vars' (Set.singleton $ rdrNameOcc x) Set.empty -- Variable pattern. - allVars' (AsPat _ n x) = allVars' (VarPat noExt n :: Pat GhcPs) <> allVars' x -- As pattern. - allVars' (ConPatIn _ (RecCon (HsRecFields flds _))) = allVars' flds - allVars' (NPlusKPat _ n _ _ _ _) = allVars' (VarPat noExt n :: Pat GhcPs) -- n+k pattern. - allVars' (ViewPat _ e p) = freeVars_' e <> allVars' p -- View pattern. - - allVars' WildPat{} = mempty -- Wildcard pattern. - allVars' ConPatOut{} = mempty -- Renamer/typechecker. - allVars' LitPat{} = mempty -- Literal pattern. - allVars' NPat{} = mempty -- Natural pattern. - - -- allVars' p@SplicePat{} = allVars' $ children p -- Splice pattern (includes quasi-quotes). - -- allVars' p@SigPat{} = allVars' $ children p -- Pattern with a type signature. - -- allVars' p@CoPat{} = allVars' $ children p -- Coercion pattern. - -- allVars' p@LazyPat{} = allVars' $ children p -- Lazy pattern. - -- allVars' p@ParPat{} = allVars' $ children p -- Parenthesized pattern. - -- allVars' p@BangPat{} = allVars' $ children p -- Bang pattern. - -- allVars' p@ListPat{} = allVars' $ children p -- Syntactic list. - -- allVars' p@TuplePat{} = allVars' $ children p -- Tuple sub patterns. - -- allVars' p@SumPat{} = allVars' $ children p -- Anonymous sum pattern. - - allVars' p = allVars' $ children p - -instance AllVars' (LHsRecField GhcPs (LPat GhcPs)) where - allVars' (dL -> L _ (HsRecField _ x _)) = allVars' x - -instance AllVars' (LStmt GhcPs (LHsExpr GhcPs)) where - allVars' (dL -> L _ (LastStmt _ expr _ _)) = freeVars_' expr -- The last stmt of a 'ListComp', 'MonadComp', 'DoExpr','MDoExpr'. - allVars' (dL -> L _ (BindStmt _ pat expr _ _)) = allVars' pat <> freeVars_' expr -- A generator e.g. 'x <- [1, 2, 3]'. - allVars' (dL -> L _ (BodyStmt _ expr _ _)) = freeVars_' expr -- A boolean guard e.g. 'even x'. - allVars' (dL -> L _ (LetStmt _ binds)) = allVars' binds -- A local declaration e.g. 'let y = x + 1' - allVars' (dL -> L _ (TransStmt _ _ stmts _ using by _ _ fmap_)) = allVars' stmts <> freeVars_' using <> maybe mempty freeVars_' by <> freeVars_' (noLoc fmap_ :: Located (HsExpr GhcPs)) -- Apply a function to a list of statements in order. - allVars' (dL -> L _ (RecStmt _ stmts _ _ _ _ _)) = allVars' stmts -- A recursive binding for a group of arrows. - - allVars' (dL -> L _ ApplicativeStmt{}) = mempty -- Generated by the renamer. - allVars' (dL -> L _ ParStmt{}) = mempty -- Parallel list thing. Come back to it. - - allVars' _ = mempty -- New ctor. - -instance AllVars' (LHsLocalBinds GhcPs) where - allVars' (dL -> L _ (HsValBinds _ (ValBinds _ binds _))) = allVars' (bagToList binds) -- Value bindings. - allVars' (dL -> L _ (HsIPBinds _ (IPBinds _ binds))) = allVars' binds -- Implicit parameter bindings. - - allVars' (dL -> L _ EmptyLocalBinds{}) = mempty -- The case of no local bindings (signals the empty `let` or `where` clause). - - allVars' _ = mempty -- New ctor. - -instance AllVars' (LIPBind GhcPs) where - allVars' (dL -> L _ (IPBind _ _ e)) = freeVars_' e - - allVars' _ = mempty -- New ctor. - -instance AllVars' (LHsBind GhcPs) where - allVars' (dL -> L _ FunBind{fun_id=n, fun_matches=MG{mg_alts=(dL -> L _ ms)}}) = allVars' (VarPat noExt n :: Pat GhcPs) <> allVars' ms -- Function bindings and simple variable bindings e.g. 'f x = e', 'f !x = 3', 'f = e', '!x = e', 'x `f` y = e' - allVars' (dL -> L _ PatBind{pat_lhs=n, pat_rhs=grhss}) = allVars' n <> allVars' grhss -- Ctor patterns and some other interesting cases e.g. 'Just x = e', '(x) = e', 'x :: Ty = e'. - - allVars' (dL -> L _ (PatSynBind _ PSB{})) = mempty -- Come back to it. - allVars' (dL -> L _ VarBind{}) = mempty -- Typechecker. - allVars' (dL -> L _ AbsBinds{}) = mempty -- Not sure but I think renamer. +unqualNames :: Located RdrName -> [OccName] +unqualNames (L _ (Unqual x)) = [x] +unqualNames (L _ (Exact x)) = [nameOccName x] +unqualNames _ = [] + +instance FreeVars (LHsExpr GhcPs) where + freeVars (L _ (HsVar _ x)) = Set.fromList $ unqualNames x -- Variable. + freeVars (L _ (HsUnboundVar _ x)) = Set.fromList [unboundVarOcc x] -- Unbound variable; also used for "holes". + freeVars (L _ (HsLam _ mg)) = free (allVars mg) -- Lambda abstraction. Currently always a single match. + freeVars (L _ (HsLamCase _ MG{mg_alts=(L _ ms)})) = free (allVars ms) -- Lambda case + freeVars (L _ (HsCase _ of_ MG{mg_alts=(L _ ms)})) = freeVars of_ ^+ free (allVars ms) -- Case expr. + freeVars (L _ (HsLet _ binds e)) = inFree binds e -- Let (rec). + freeVars (L _ (HsDo _ ctxt (L _ stmts))) = free (allVars stmts) -- Do block. + freeVars (L _ (RecordCon _ _ (HsRecFields flds _))) = Set.unions $ map freeVars flds -- Record construction. + freeVars (L _ (RecordUpd _ e flds)) = Set.unions $ freeVars e : map freeVars flds -- Record update. + freeVars (L _ (HsMultiIf _ grhss)) = free (allVars grhss) -- Multi-way if. + + freeVars (L _ HsConLikeOut{}) = mempty -- After typechecker. + freeVars (L _ HsRecFld{}) = mempty -- Variable pointing to a record selector. + freeVars (L _ HsOverLabel{}) = mempty -- Overloaded label. The id of the in-scope fromLabel. + freeVars (L _ HsIPVar{}) = mempty -- Implicit parameter. + freeVars (L _ HsOverLit{}) = mempty -- Overloaded literal. + freeVars (L _ HsLit{}) = mempty -- Simple literal. + freeVars (L _ HsRnBracketOut{}) = mempty -- Renamer produces these. + freeVars (L _ HsTcBracketOut{}) = mempty -- Typechecker produces these. + freeVars (L _ HsWrap{}) = mempty -- Typechecker output. + + -- freeVars (e@(L _ HsAppType{})) = freeVars $ children e -- Visible type application e.g. f @ Int x y. + -- freeVars (e@(L _ HsApp{})) = freeVars $ children e -- Application. + -- freeVars (e@(L _ OpApp{})) = freeVars $ children e -- Operator application. + -- freeVars (e@(L _ NegApp{})) = freeVars $ children e -- Negation operator. + -- freeVars (e@(L _ HsPar{})) = freeVars $ children e -- Parenthesized expr. + -- freeVars (e@(L _ SectionL{})) = freeVars $ children e -- Left section. + -- freeVars (e@(L _ SectionR{})) = freeVars $ children e -- Right section. + -- freeVars (e@(L _ ExplicitTuple{})) = freeVars $ children e -- Explicit tuple and sections thereof. + -- freeVars (e@(L _ ExplicitSum{})) = freeVars $ children e -- Used for unboxed sum types. + -- freeVars (e@(L _ HsIf{})) = freeVars $ children e -- If. + -- freeVars (e@(L _ ExplicitList{})) = freeVars $ children e -- Syntactic list e.g. [a, b, c]. + -- freeVars (e@(L _ ExprWithTySig{})) = freeVars $ children e -- Expr with type signature. + -- freeVars (e@(L _ ArithSeq {})) = freeVars $ children e -- Arithmetic sequence. + -- freeVars (e@(L _ HsSCC{})) = freeVars $ children e -- Set cost center pragma (expr whose const is to be measured). + -- freeVars (e@(L _ HsCoreAnn{})) = freeVars $ children e -- Pragma. + -- freeVars (e@(L _ HsBracket{})) = freeVars $ children e -- Haskell bracket. + -- freeVars (e@(L _ HsSpliceE{})) = freeVars $ children e -- Template haskell splice expr. + -- freeVars (e@(L _ HsProc{})) = freeVars $ children e -- Proc notation for arrows. + -- freeVars (e@(L _ HsStatic{})) = freeVars $ children e -- Static pointers extension. + -- freeVars (e@(L _ HsArrApp{})) = freeVars $ children e -- Arrow tail or arrow application. + -- freeVars (e@(L _ HsArrForm{})) = freeVars $ children e -- Come back to it. Arrow tail or arrow application. + -- freeVars (e@(L _ HsTick{})) = freeVars $ children e -- Haskell program coverage (Hpc) support. + -- freeVars (e@(L _ HsBinTick{})) = freeVars $ children e -- Haskell program coverage (Hpc) support. + -- freeVars (e@(L _ HsTickPragma{})) = freeVars $ children e -- Haskell program coverage (Hpc) support. + -- freeVars (e@(L _ EAsPat{})) = freeVars $ children e -- Expr as pat. + -- freeVars (e@(L _ EViewPat{})) = freeVars $ children e -- View pattern. + -- freeVars (e@(L _ ELazyPat{})) = freeVars $ children e -- Lazy pattern. + + freeVars e = freeVars $ children e + +instance FreeVars (LHsTupArg GhcPs) where + freeVars (L _ (Present _ args)) = freeVars args + freeVars _ = mempty + +instance FreeVars (LHsRecField GhcPs (LHsExpr GhcPs)) where + freeVars o@(L _ (HsRecField x _ True)) = Set.singleton $ occName $ unLoc $ rdrNameFieldOcc $ unLoc x -- a pun + freeVars o@(L _ (HsRecField _ x _)) = freeVars x + +instance FreeVars (LHsRecUpdField GhcPs) where + freeVars (L _ (HsRecField _ x _)) = freeVars x + +instance AllVars (Located (Pat GhcPs)) where + allVars (L _ (VarPat _ (L _ x))) = Vars (Set.singleton $ rdrNameOcc x) Set.empty -- Variable pattern. + allVars (L _ (AsPat _ n x)) = allVars (noLoc $ VarPat noExtField n :: LPat GhcPs) <> allVars x -- As pattern. + allVars (L _ (ConPatIn _ (RecCon (HsRecFields flds _)))) = allVars flds + allVars (L _ (NPlusKPat _ n _ _ _ _)) = allVars (noLoc $ VarPat noExtField n :: LPat GhcPs) -- n+k pattern. + allVars (L _ (ViewPat _ e p)) = freeVars_ e <> allVars p -- View pattern. + + allVars (L _ WildPat{}) = mempty -- Wildcard pattern. + allVars (L _ ConPatOut{}) = mempty -- Renamer/typechecker. + allVars (L _ LitPat{}) = mempty -- Literal pattern. + allVars (L _ NPat{}) = mempty -- Natural pattern. + + -- allVars p@SplicePat{} = allVars $ children p -- Splice pattern (includes quasi-quotes). + -- allVars p@SigPat{} = allVars $ children p -- Pattern with a type signature. + -- allVars p@CoPat{} = allVars $ children p -- Coercion pattern. + -- allVars p@LazyPat{} = allVars $ children p -- Lazy pattern. + -- allVars p@ParPat{} = allVars $ children p -- Parenthesized pattern. + -- allVars p@BangPat{} = allVars $ children p -- Bang pattern. + -- allVars p@ListPat{} = allVars $ children p -- Syntactic list. + -- allVars p@TuplePat{} = allVars $ children p -- Tuple sub patterns. + -- allVars p@SumPat{} = allVars $ children p -- Anonymous sum pattern. + + allVars p = allVars $ children p + +instance AllVars (LHsRecField GhcPs (Located (Pat GhcPs))) where + allVars (L _ (HsRecField _ x _)) = allVars x + +instance AllVars (LStmt GhcPs (LHsExpr GhcPs)) where + allVars (L _ (LastStmt _ expr _ _)) = freeVars_ expr -- The last stmt of a ListComp, MonadComp, DoExpr,MDoExpr. + allVars (L _ (BindStmt _ pat expr _ _)) = allVars pat <> freeVars_ expr -- A generator e.g. x <- [1, 2, 3]. + allVars (L _ (BodyStmt _ expr _ _)) = freeVars_ expr -- A boolean guard e.g. even x. + allVars (L _ (LetStmt _ binds)) = allVars binds -- A local declaration e.g. let y = x + 1 + allVars (L _ (TransStmt _ _ stmts _ using by _ _ fmap_)) = allVars stmts <> freeVars_ using <> maybe mempty freeVars_ by <> freeVars_ (noLoc fmap_ :: Located (HsExpr GhcPs)) -- Apply a function to a list of statements in order. + allVars (L _ (RecStmt _ stmts _ _ _ _ _)) = allVars stmts -- A recursive binding for a group of arrows. + + allVars (L _ ApplicativeStmt{}) = mempty -- Generated by the renamer. + allVars (L _ ParStmt{}) = mempty -- Parallel list thing. Come back to it. + + allVars _ = mempty -- New ctor. + +instance AllVars (LHsLocalBinds GhcPs) where + allVars (L _ (HsValBinds _ (ValBinds _ binds _))) = allVars (bagToList binds) -- Value bindings. + allVars (L _ (HsIPBinds _ (IPBinds _ binds))) = allVars binds -- Implicit parameter bindings. + + allVars (L _ EmptyLocalBinds{}) = mempty -- The case of no local bindings (signals the empty `let` or `where` clause). + + allVars _ = mempty -- New ctor. + +instance AllVars (LIPBind GhcPs) where + allVars (L _ (IPBind _ _ e)) = freeVars_ e + + allVars _ = mempty -- New ctor. + +instance AllVars (LHsBind GhcPs) where + allVars (L _ FunBind{fun_id=n, fun_matches=MG{mg_alts=(L _ ms)}}) = allVars (noLoc $ VarPat noExtField n :: LPat GhcPs) <> allVars ms -- Function bindings and simple variable bindings e.g. f x = e, f !x = 3, f = e, !x = e, x `f` y = e + allVars (L _ PatBind{pat_lhs=n, pat_rhs=grhss}) = allVars n <> allVars grhss -- Ctor patterns and some other interesting cases e.g. Just x = e, (x) = e, x :: Ty = e. + + allVars (L _ (PatSynBind _ PSB{})) = mempty -- Come back to it. + allVars (L _ VarBind{}) = mempty -- Typechecker. + allVars (L _ AbsBinds{}) = mempty -- Not sure but I think renamer. - allVars' _ = mempty -- New ctor. + allVars _ = mempty -- New ctor. -instance AllVars' (MatchGroup GhcPs (LHsExpr GhcPs)) where - allVars' (MG _ _alts@(dL -> L _ alts) _) = inVars' (foldMap (allVars' . m_pats) ms) (allVars' (map m_grhss ms)) +instance AllVars (MatchGroup GhcPs (LHsExpr GhcPs)) where + allVars (MG _ _alts@(L _ alts) _) = inVars (foldMap (allVars . m_pats) ms) (allVars (map m_grhss ms)) where ms = map unLoc alts - allVars' _ = mempty -- New ctor. + allVars _ = mempty -- New ctor. -instance AllVars' (LMatch GhcPs (LHsExpr GhcPs)) where - allVars' (dL -> L _ (Match _ FunRhs {mc_fun=name} pats grhss)) = allVars' (VarPat noExt name :: Pat GhcPs) <> allVars' pats <> allVars' grhss -- A pattern matching on an argument of a function binding. - allVars' (dL -> L _ (Match _ (StmtCtxt ctxt) pats grhss)) = allVars' ctxt <> allVars' pats <> allVars' grhss -- Pattern of a do-stmt, list comprehension, pattern guard etc. - allVars' (dL -> L _ (Match _ _ pats grhss)) = inVars' (allVars' pats) (allVars' grhss) -- Everything else. +instance AllVars (LMatch GhcPs (LHsExpr GhcPs)) where + allVars (L _ (Match _ FunRhs {mc_fun=name} pats grhss)) = allVars (noLoc $ VarPat noExtField name :: LPat GhcPs) <> allVars pats <> allVars grhss -- A pattern matching on an argument of a function binding. + allVars (L _ (Match _ (StmtCtxt ctxt) pats grhss)) = allVars ctxt <> allVars pats <> allVars grhss -- Pattern of a do-stmt, list comprehension, pattern guard etc. + allVars (L _ (Match _ _ pats grhss)) = inVars (allVars pats) (allVars grhss) -- Everything else. - allVars' _ = mempty -- New ctor. + allVars _ = mempty -- New ctor. -instance AllVars' (HsStmtContext RdrName) where - allVars' (PatGuard FunRhs{mc_fun=n}) = allVars' (VarPat noExt n :: Pat GhcPs) - allVars' ParStmtCtxt{} = mempty -- Come back to it. - allVars' TransStmtCtxt{} = mempty -- Come back to it. +instance AllVars (HsStmtContext RdrName) where + allVars (PatGuard FunRhs{mc_fun=n}) = allVars (noLoc $ VarPat noExtField n :: LPat GhcPs) + allVars ParStmtCtxt{} = mempty -- Come back to it. + allVars TransStmtCtxt{} = mempty -- Come back to it. - allVars' _ = mempty -- Everything else (correct). + allVars _ = mempty -- Everything else (correct). -instance AllVars' (GRHSs GhcPs (LHsExpr GhcPs)) where - allVars' (GRHSs _ grhss binds) = inVars' binds (mconcat (map allVars' grhss)) +instance AllVars (GRHSs GhcPs (LHsExpr GhcPs)) where + allVars (GRHSs _ grhss binds) = inVars binds (mconcatMap allVars grhss) - allVars' _ = mempty -- New ctor. + allVars _ = mempty -- New ctor. -instance AllVars' (LGRHS GhcPs (LHsExpr GhcPs)) where - allVars' (dL -> L _ (GRHS _ guards expr)) = Vars' (bound' gs) (free' gs ^+ (freeVars' expr ^- bound' gs)) where gs = allVars' guards +instance AllVars (LGRHS GhcPs (LHsExpr GhcPs)) where + allVars (L _ (GRHS _ guards expr)) = Vars (bound gs) (free gs ^+ (freeVars expr ^- bound gs)) where gs = allVars guards - allVars' _ = mempty -- New ctor. + allVars _ = mempty -- New ctor. -instance AllVars' (LHsDecl GhcPs) where - allVars' (dL -> L l (ValD _ bind)) = allVars' (cL l bind :: LHsBind GhcPs) +instance AllVars (LHsDecl GhcPs) where + allVars (L l (ValD _ bind)) = allVars (L l bind :: LHsBind GhcPs) - allVars' _ = mempty -- We only consider value bindings. + allVars _ = mempty -- We only consider value bindings. --- -vars' :: FreeVars' a => a -> [String] -vars' = Set.toList . Set.map occNameString . freeVars' +vars :: FreeVars a => a -> [String] +vars = Set.toList . Set.map occNameString . freeVars -varss' :: AllVars' a => a -> [String] -varss' = Set.toList . Set.map occNameString . free' . allVars' +varss :: AllVars a => a -> [String] +varss = Set.toList . Set.map occNameString . free . allVars -pvars' :: AllVars' a => a -> [String] -pvars' = Set.toList . Set.map occNameString . bound' . allVars' - -freeVarSet' :: FreeVars' a => a -> Set String -freeVarSet' = Set.map occNameString . freeVars' +pvars :: AllVars a => a -> [String] +pvars = Set.toList . Set.map occNameString . bound . allVars diff -Nru hlint-2.2.11/src/GHC/Util/HsDecl.hs hlint-3.1.6/src/GHC/Util/HsDecl.hs --- hlint-2.2.11/src/GHC/Util/HsDecl.hs 2020-01-27 16:53:20.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/HsDecl.hs 2020-05-21 15:48:31.000000000 +0000 @@ -1,19 +1,11 @@ {-# LANGUAGE NamedFieldPuns #-} -module GHC.Util.HsDecl (declName,bindName,isForD',isNewType',isDerivD',isClsDefSig') +module GHC.Util.HsDecl (declName,bindName) where -import HsSyn -import OccName +import GHC.Hs import SrcLoc - -isNewType' :: NewOrData -> Bool -isNewType' NewType = True -isNewType' DataType = False - -isForD', isDerivD' :: LHsDecl GhcPs -> Bool -isForD' (LL _ ForD{}) = True; isForD' _ = False -isDerivD' (LL _ DerivD{}) = True; isDerivD' _ = False +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader -- | @declName x@ returns the \"new name\" that is created (for -- example a function declaration) by @x@. If @x@ isn't a declaration @@ -22,7 +14,7 @@ -- want to tell users to rename binders that they aren't creating -- right now and therefore usually cannot change. declName :: LHsDecl GhcPs -> Maybe String -declName (LL _ x) = occNameString . occName <$> case x of +declName (L _ x) = occNameStr <$> case x of TyClD _ FamDecl{tcdFam=FamilyDecl{fdLName}} -> Just $ unLoc fdLName TyClD _ SynDecl{tcdLName} -> Just $ unLoc tcdLName TyClD _ DataDecl{tcdLName} -> Just $ unLoc tcdLName @@ -36,13 +28,9 @@ ForD _ ForeignImport{fd_name} -> Just $ unLoc fd_name ForD _ ForeignExport{fd_name} -> Just $ unLoc fd_name _ -> Nothing -declName _ = Nothing {- COMPLETE LL-} bindName :: LHsBind GhcPs -> Maybe String -bindName (LL _ FunBind{fun_id}) = Just $ occNameString $ occName $ unLoc fun_id -bindName (LL _ VarBind{var_id}) = Just $ occNameString $ occName var_id +bindName (L _ FunBind{fun_id}) = Just $ rdrNameStr fun_id +bindName (L _ VarBind{var_id}) = Just $ occNameStr var_id bindName _ = Nothing - -isClsDefSig' :: Sig GhcPs -> Bool -isClsDefSig' (ClassOpSig _ True _ _) = True; isClsDefSig' _ = False diff -Nru hlint-2.2.11/src/GHC/Util/HsExpr.hs hlint-3.1.6/src/GHC/Util/HsExpr.hs --- hlint-2.2.11/src/GHC/Util/HsExpr.hs 2020-02-09 21:16:43.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/HsExpr.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,22 +1,20 @@ {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ViewPatterns, MultiParamTypeClasses , FlexibleInstances, FlexibleContexts #-} +{-# LANGUAGE ViewPatterns, MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE TupleSections #-} --- Keep until 'descendApps', 'transformApps' and 'allowLeftSection' --- are used. -{-# OPTIONS_GHC -Wno-unused-top-binds #-} - module GHC.Util.HsExpr ( - dotApp', dotApps' - , simplifyExp', niceLambda', niceDotApp' - , Brackets'(..) - , rebracket1', appsBracket', transformAppsM', fromApps', apps', universeApps', universeParentExp' - , paren' - , replaceBranches' - , needBracketOld', transformBracketOld', descendBracketOld', reduce', reduce1', fromParen1' + dotApps, lambda + , simplifyExp, niceLambda, niceLambdaR + , Brackets(..) + , rebracket1, appsBracket, transformAppsM, fromApps, apps, universeApps, universeParentExp + , paren + , replaceBranches + , needBracketOld, transformBracketOld, fromParen1 + , allowLeftSection, allowRightSection ) where -import HsSyn +import GHC.Hs import BasicTypes import SrcLoc import FastString @@ -25,214 +23,267 @@ import Bag(bagToList) import GHC.Util.Brackets -import GHC.Util.View import GHC.Util.FreeVars -import GHC.Util.Pat +import GHC.Util.View import Control.Applicative import Control.Monad.Trans.State import Data.Data -import Data.Generics.Uniplate.Data +import Data.Generics.Uniplate.DataOnly import Data.List.Extra import Data.Tuple.Extra -import Refact.Types hiding (Match) +import Refact (toSS) +import Refact.Types hiding (SrcSpan, Match) import qualified Refact.Types as R (SrcSpan) +import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader -- | 'dotApp a b' makes 'a . b'. -dotApp' :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs -dotApp' x y = noLoc $ OpApp noExt x (noLoc $ HsVar noExt (noLoc $ mkVarUnqual (fsLit "."))) y +dotApp :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs +dotApp x y = noLoc $ OpApp noExtField x (noLoc $ HsVar noExtField (noLoc $ mkVarUnqual (fsLit "."))) y -dotApps' :: [LHsExpr GhcPs] -> LHsExpr GhcPs -dotApps' [] = error "GHC.Util.HsExpr.dotApps', does not work on an empty list" -dotApps' [x] = x -dotApps' (x : xs) = dotApp' x (dotApps' xs) +dotApps :: [LHsExpr GhcPs] -> LHsExpr GhcPs +dotApps [] = error "GHC.Util.HsExpr.dotApps', does not work on an empty list" +dotApps [x] = x +dotApps (x : xs) = dotApp x (dotApps xs) + +-- | @lambda [p0, p1..pn] body@ makes @\p1 p1 .. pn -> body@ +lambda :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsExpr GhcPs +lambda vs body = noLoc $ HsLam noExtField (MG noExtField (noLoc [noLoc $ Match noExtField LambdaExpr vs (GRHSs noExtField [noLoc $ GRHS noExtField [] body] (noLoc $ EmptyLocalBinds noExtField))]) Generated) -- | 'paren e' wraps 'e' in parens if 'e' is non-atomic. -paren' :: LHsExpr GhcPs -> LHsExpr GhcPs -paren' x - | isAtom' x = x - | otherwise = addParen' x - -universeParentExp' :: Data a => a -> [(Maybe (Int, LHsExpr GhcPs), LHsExpr GhcPs)] -universeParentExp' xs = concat [(Nothing, x) : f x | x <- childrenBi xs] - where f p = concat [(Just (i,p), c) : f c | (i,c) <- zip [0..] $ children p] - +paren :: LHsExpr GhcPs -> LHsExpr GhcPs +paren x + | isAtom x = x + | otherwise = addParen x -apps' :: [LHsExpr GhcPs] -> LHsExpr GhcPs -apps' = foldl1' mkApp where mkApp x y = noLoc (HsApp noExt x y) +universeParentExp :: Data a => a -> [(Maybe (Int, LHsExpr GhcPs), LHsExpr GhcPs)] +universeParentExp xs = concat [(Nothing, x) : f x | x <- childrenBi xs] + where f p = concat [(Just (i,p), c) : f c | (i,c) <- zipFrom 0 $ children p] -fromApps' :: LHsExpr GhcPs -> [LHsExpr GhcPs] -fromApps' (LL _ (HsApp _ x y)) = fromApps' x ++ [y] -fromApps' x = [x] -childrenApps' :: LHsExpr GhcPs -> [LHsExpr GhcPs] -childrenApps' (LL _ (HsApp _ x y)) = childrenApps' x ++ [y] -childrenApps' x = children x +apps :: [LHsExpr GhcPs] -> LHsExpr GhcPs +apps = foldl1' mkApp where mkApp x y = noLoc (HsApp noExtField x y) -universeApps' :: LHsExpr GhcPs -> [LHsExpr GhcPs] -universeApps' x = x : concatMap universeApps' (childrenApps' x) +fromApps :: LHsExpr GhcPs -> [LHsExpr GhcPs] +fromApps (L _ (HsApp _ x y)) = fromApps x ++ [y] +fromApps x = [x] -descendApps' :: (LHsExpr GhcPs -> LHsExpr GhcPs) -> LHsExpr GhcPs -> LHsExpr GhcPs -descendApps' f (LL l (HsApp _ x y)) = LL l $ HsApp noExt (descendApps' f x) (f y) -descendApps' f x = descend f x +childrenApps :: LHsExpr GhcPs -> [LHsExpr GhcPs] +childrenApps (L _ (HsApp _ x y)) = childrenApps x ++ [y] +childrenApps x = children x -descendAppsM' :: Monad m => (LHsExpr GhcPs -> m (LHsExpr GhcPs)) -> LHsExpr GhcPs -> m (LHsExpr GhcPs) -descendAppsM' f (LL l (HsApp _ x y)) = liftA2 (\x y -> LL l $ HsApp noExt x y) (descendAppsM' f x) (f y) -descendAppsM' f x = descendM f x +universeApps :: LHsExpr GhcPs -> [LHsExpr GhcPs] +universeApps x = x : concatMap universeApps (childrenApps x) -transformApps' :: (LHsExpr GhcPs -> LHsExpr GhcPs) -> LHsExpr GhcPs -> LHsExpr GhcPs -transformApps' f = f . descendApps' (transformApps' f) +descendAppsM :: Monad m => (LHsExpr GhcPs -> m (LHsExpr GhcPs)) -> LHsExpr GhcPs -> m (LHsExpr GhcPs) +descendAppsM f (L l (HsApp _ x y)) = liftA2 (\x y -> L l $ HsApp noExtField x y) (descendAppsM f x) (f y) +descendAppsM f x = descendM f x -transformAppsM' :: Monad m => (LHsExpr GhcPs -> m (LHsExpr GhcPs)) -> LHsExpr GhcPs -> m (LHsExpr GhcPs) -transformAppsM' f x = f =<< descendAppsM' (transformAppsM' f) x +transformAppsM :: Monad m => (LHsExpr GhcPs -> m (LHsExpr GhcPs)) -> LHsExpr GhcPs -> m (LHsExpr GhcPs) +transformAppsM f x = f =<< descendAppsM (transformAppsM f) x -descendIndex' :: Data a => (Int -> a -> a) -> a -> a -descendIndex' f x = flip evalState 0 $ flip descendM x $ \y -> do +descendIndex :: Data a => (Int -> a -> a) -> a -> a +descendIndex f x = flip evalState 0 $ flip descendM x $ \y -> do i <- get modify (+1) - return $ f i y + pure $ f i y -- There are differences in pretty-printing between GHC and HSE. This -- version never removes brackets. -descendBracket' :: (LHsExpr GhcPs -> (Bool, LHsExpr GhcPs)) -> LHsExpr GhcPs -> LHsExpr GhcPs -descendBracket' op x = descendIndex' g x +descendBracket :: (LHsExpr GhcPs -> (Bool, LHsExpr GhcPs)) -> LHsExpr GhcPs -> LHsExpr GhcPs +descendBracket op x = descendIndex g x where g i y = if a then f i b else b where (a, b) = op y - f i y@(LL _ e) | needBracket' i x y = addParen' y + f i y@(L _ e) | needBracket i x y = addParen y f _ y = y --- Add brackets as suggested 'needBracket' at 1-level of depth. -rebracket1' :: LHsExpr GhcPs -> LHsExpr GhcPs -rebracket1' = descendBracket' (True, ) +-- Add brackets as suggested 'needBracket at 1-level of depth. +rebracket1 :: LHsExpr GhcPs -> LHsExpr GhcPs +rebracket1 = descendBracket (True, ) -- A list of application, with any necessary brackets. -appsBracket' :: [LHsExpr GhcPs] -> LHsExpr GhcPs -appsBracket' = foldl1 mkApp - where mkApp x y = rebracket1' (noLoc $ HsApp noExt x y) +appsBracket :: [LHsExpr GhcPs] -> LHsExpr GhcPs +appsBracket = foldl1 mkApp + where mkApp x y = rebracket1 (noLoc $ HsApp noExtField x y) -simplifyExp' :: LHsExpr GhcPs -> LHsExpr GhcPs +simplifyExp :: LHsExpr GhcPs -> LHsExpr GhcPs -- Replace appliciations 'f $ x' with 'f (x)'. -simplifyExp' (LL l (OpApp _ x op y)) | isDol op = LL l (HsApp noExt x (noLoc (HsPar noExt y))) -simplifyExp' e@(LL _ (HsLet _ (LL _ (HsValBinds _ (ValBinds _ binds []))) z)) = +simplifyExp (L l (OpApp _ x op y)) | isDol op = L l (HsApp noExtField x (noLoc (HsPar noExtField y))) +simplifyExp e@(L _ (HsLet _ (L _ (HsValBinds _ (ValBinds _ binds []))) z)) = -- An expression of the form, 'let x = y in z'. case bagToList binds of - [LL _ (FunBind _ _(MG _ (LL _ [LL _ (Match _(FunRhs (LL _ x) _ _) [] (GRHSs _[LL _ (GRHS _ [] y)] (LL _ (EmptyLocalBinds _))))]) _) _ _)] + [L _ (FunBind _ _(MG _ (L _ [L _ (Match _(FunRhs (L _ x) _ _) [] (GRHSs _[L _ (GRHS _ [] y)] (L _ (EmptyLocalBinds _))))]) _) _ _)] -- If 'x' is not in the free variables of 'y', beta-reduce to -- 'z[(y)/x]'. - | occNameString (rdrNameOcc x) `notElem` vars' y && length [() | Unqual a <- universeBi z, a == rdrNameOcc x] <= 1 -> + | occNameStr x `notElem` vars y && length [() | Unqual a <- universeBi z, a == rdrNameOcc x] <= 1 -> transform f z - where f (view' -> Var_' x') | occNameString (rdrNameOcc x) == x' = paren' y + where f (view -> Var_ x') | occNameStr x == x' = paren y f x = x _ -> e -simplifyExp' e = e +simplifyExp e = e -- Rewrite '($) . b' as 'b'. -niceDotApp' :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs -niceDotApp' (LL _ (HsVar _ (L _ r))) b | occNameString (rdrNameOcc r) == "$" = b -niceDotApp' a b = dotApp' a b +niceDotApp :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs +niceDotApp (L _ (HsVar _ (L _ r))) b | occNameStr r == "$" = b +niceDotApp a b = dotApp a b -- Generate a lambda expression but prettier if possible. -niceLambda' :: [String] -> LHsExpr GhcPs -> LHsExpr GhcPs -niceLambda' ss e = fst (niceLambdaR' ss e)-- We don't support refactorings yet. +niceLambda :: [String] -> LHsExpr GhcPs -> LHsExpr GhcPs +niceLambda ss e = fst (niceLambdaR ss e)-- We don't support refactorings yet. +allowRightSection :: String -> Bool allowRightSection x = x `notElem` ["-","#"] +allowLeftSection :: String -> Bool allowLeftSection x = x /= "#" -- Implementation. Try to produce special forms (e.g. sections, -- compositions) where we can. -niceLambdaR' :: [String] - -> LHsExpr GhcPs - -> (LHsExpr GhcPs, R.SrcSpan - -> [Refactoring R.SrcSpan]) --- Rewrite '\xs -> (e)' as '\xs -> e'. -niceLambdaR' xs (LL _ (HsPar _ x)) = niceLambdaR' xs x --- Rewrite '\x -> x + a' as '(+ a)' (heuristic: 'a' must be a single +niceLambdaR :: [String] + -> LHsExpr GhcPs + -> (LHsExpr GhcPs, R.SrcSpan -> [Refactoring R.SrcSpan]) +-- Rewrite @\ -> e@ as @e@ +-- These are encountered as recursive calls. +niceLambdaR xs (SimpleLambda [] x) = niceLambdaR xs x + +-- Rewrite @\xs -> (e)@ as @\xs -> e@. +niceLambdaR xs (L _ (HsPar _ x)) = niceLambdaR xs x + +-- @\vs v -> ($) e v@ ==> @\vs -> e@ +-- @\vs v -> e $ v@ ==> @\vs -> e@ +niceLambdaR (unsnoc -> Just (vs, v)) (view -> App2 f e (view -> Var_ v')) + | isDol f + , v == v' + , vars e `disjoint` [v] + = niceLambdaR vs e + +-- @\v -> thing + v@ ==> @\v -> (thing +)@ (heuristic: @v@ must be a single +-- lexeme, or it all gets too complex) +niceLambdaR [v] (L _ (OpApp _ e f (view -> Var_ v'))) + | isLexeme e + , v == v' + , vars e `disjoint` [v] + , L _ (HsVar _ (L _ fname)) <- f + , isSymOcc $ rdrNameOcc fname + = let res = noLoc $ HsPar noExtField $ noLoc $ SectionL noExtField e f + in (res, \s -> [Replace Expr s [] (unsafePrettyPrint res)]) + +-- @\vs v -> f x v@ ==> @\vs -> f x@ +niceLambdaR (unsnoc -> Just (vs, v)) (L _ (HsApp _ f (view -> Var_ v'))) + | v == v' + , vars f `disjoint` [v] + = niceLambdaR vs f + +-- @\vs v -> (v `f`)@ ==> @\vs -> f@ +niceLambdaR (unsnoc -> Just (vs, v)) (L _ (SectionL _ (view -> Var_ v') f)) + | v == v' = niceLambdaR vs f + +-- Strip one variable pattern from the end of a lambdas match, and place it in our list of factoring variables. +niceLambdaR xs (SimpleLambda ((view -> PVar_ v):vs) x) + | v `notElem` xs = niceLambdaR (xs++[v]) $ lambda vs x + +-- Rewrite @\x -> x + a@ as @(+ a)@ (heuristic: @a@ must be a single -- lexeme, or it all gets too complex). -niceLambdaR' [x] (view' -> App2' op@(LL _ (HsVar _ (L _ tag))) l r) - | isLexeme r, view' l == Var_' x, x `notElem` vars' r, allowRightSection (occNameString $ rdrNameOcc tag) = - let e = rebracket1' $ addParen' (noLoc $ SectionR noExt op r) - in (e, const []) --- Rewrite (1) '\x -> f (b x)' as 'f . b', (2) '\x -> f $ b x' as 'f . b'. -niceLambdaR' [x] y - | Just (z, subts) <- factor y, x `notElem` vars' z = (z, const []) +niceLambdaR [x] (view -> App2 op@(L _ (HsVar _ (L _ tag))) l r) + | isLexeme r, view l == Var_ x, x `notElem` vars r, allowRightSection (occNameStr tag) = + let e = rebracket1 $ addParen (noLoc $ SectionR noExtField op r) + in (e, \s -> [Replace Expr s [] (unsafePrettyPrint e)]) +-- Rewrite (1) @\x -> f (b x)@ as @f . b@, (2) @\x -> f $ b x@ as @f . b@. +niceLambdaR [x] y + | Just (z, subts) <- factor y, x `notElem` vars z = (z, \s -> [mkRefact subts s]) where -- Factor the expression with respect to x. factor :: LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [LHsExpr GhcPs]) - factor y@(LL _ (HsApp _ ini lst)) | view' lst == Var_' x = Just (ini, [ini]) - factor y@(LL _ (HsApp _ ini lst)) | Just (z, ss) <- factor lst - = let r = niceDotApp' ini z + factor y@(L _ (HsApp _ ini lst)) | view lst == Var_ x = Just (ini, [ini]) + factor y@(L _ (HsApp _ ini lst)) | Just (z, ss) <- factor lst + = let r = niceDotApp ini z in if astEq r z then Just (r, ss) else Just (r, ini : ss) - factor (LL _ (OpApp _ y op (factor -> Just (z, ss))))| isDol op - = let r = niceDotApp' y z + factor (L _ (OpApp _ y op (factor -> Just (z, ss))))| isDol op + = let r = niceDotApp y z in if astEq r z then Just (r, ss) else Just (r, y : ss) - factor (LL _ (HsPar _ y@(LL _ HsApp{}))) = factor y + factor (L _ (HsPar _ y@(L _ HsApp{}))) = factor y factor _ = Nothing --- Rewrite '\x y -> x + y' as '(+)'. -niceLambdaR' [x,y] (LL _ (OpApp _ (view' -> Var_' x1) op@(LL _ HsVar {}) (view' -> Var_' y1))) - | x == x1, y == y1, vars' op `disjoint` [x, y] = (op, const []) --- Rewrite '\x y -> f y x' as 'flip f'. -niceLambdaR' [x, y] (view' -> App2' op (view' -> Var_' y1) (view' -> Var_' x1)) - | x == x1, y == y1, vars' op `disjoint` [x, y] = (noLoc $ HsApp noExt (strToVar "flip") op, const []) + mkRefact :: [LHsExpr GhcPs] -> R.SrcSpan -> Refactoring R.SrcSpan + mkRefact subts s = + let tempSubts = zipWith (\a b -> ([a], toSS b)) ['a' .. 'z'] subts + template = dotApps (map (strToVar . fst) tempSubts) + in Replace Expr s tempSubts (unsafePrettyPrint template) +-- Rewrite @\x y -> x + y@ as @(+)@. +niceLambdaR [x,y] (L _ (OpApp _ (view -> Var_ x1) op@(L _ HsVar {}) (view -> Var_ y1))) + | x == x1, y == y1, vars op `disjoint` [x, y] = (op, \s -> [Replace Expr s [] (unsafePrettyPrint op)]) +-- Rewrite @\x y -> f y x@ as @flip f@. +niceLambdaR [x, y] (view -> App2 op (view -> Var_ y1) (view -> Var_ x1)) + | x == x1, y == y1, vars op `disjoint` [x, y] = + ( gen op + , \s -> [Replace Expr s [("x", toSS op)] (unsafePrettyPrint $ gen (strToVar "x"))] + ) + where + gen = noLoc . HsApp noExtField (strToVar "flip") + +-- We're done factoring, but have no variables left, so we shouldn't make a lambda. +-- @\ -> e@ ==> @e@ +niceLambdaR [] e = (e, const []) -- Base case. Just a good old fashioned lambda. -niceLambdaR' ss e = - let grhs = noLoc $ GRHS noExt [] e :: LGRHS GhcPs (LHsExpr GhcPs) - grhss = GRHSs {grhssExt = noExt, grhssGRHSs=[grhs], grhssLocalBinds=noLoc $ EmptyLocalBinds noExt} - match = noLoc $ Match {m_ext=noExt, m_ctxt=LambdaExpr, m_pats=map strToPat' ss, m_grhss=grhss} :: LMatch GhcPs (LHsExpr GhcPs) - matchGroup = MG {mg_ext=noExt, mg_origin=Generated, mg_alts=noLoc [match]} - in (noLoc $ HsLam noExt matchGroup, const []) +niceLambdaR ss e = + let grhs = noLoc $ GRHS noExtField [] e :: LGRHS GhcPs (LHsExpr GhcPs) + grhss = GRHSs {grhssExt = noExtField, grhssGRHSs=[grhs], grhssLocalBinds=noLoc $ EmptyLocalBinds noExtField} + match = noLoc $ Match {m_ext=noExtField, m_ctxt=LambdaExpr, m_pats=map strToPat ss, m_grhss=grhss} :: LMatch GhcPs (LHsExpr GhcPs) + matchGroup = MG {mg_ext=noExtField, mg_origin=Generated, mg_alts=noLoc [match]} + in (noLoc $ HsLam noExtField matchGroup, const []) -- 'case' and 'if' expressions have branches, nothing else does (this -- doesn't consider 'HsMultiIf' perhaps it should?). -replaceBranches' :: LHsExpr GhcPs -> ([LHsExpr GhcPs], [LHsExpr GhcPs] -> LHsExpr GhcPs) -replaceBranches' (LL l (HsIf _ _ a b c)) = ([b, c], \[b, c] -> cL l (HsIf noExt Nothing a b c)) +replaceBranches :: LHsExpr GhcPs -> ([LHsExpr GhcPs], [LHsExpr GhcPs] -> LHsExpr GhcPs) +replaceBranches (L l (HsIf _ _ a b c)) = ([b, c], \[b, c] -> cL l (HsIf noExtField Nothing a b c)) -replaceBranches' (LL s (HsCase _ a (MG _ (L l bs) FromSource))) = - (concatMap f bs, \xs -> cL s (HsCase noExt a (MG noExt (cL l (g bs xs)) Generated))) +replaceBranches (L s (HsCase _ a (MG _ (L l bs) FromSource))) = + (concatMap f bs, \xs -> cL s (HsCase noExtField a (MG noExtField (cL l (g bs xs)) Generated))) where f :: LMatch GhcPs (LHsExpr GhcPs) -> [LHsExpr GhcPs] - f (LL _ (Match _ CaseAlt _ (GRHSs _ xs _))) = [x | (LL _ (GRHS _ _ x)) <- xs] - f _ = undefined -- {-# COMPLETE LL #-} + f (L _ (Match _ CaseAlt _ (GRHSs _ xs _))) = [x | (L _ (GRHS _ _ x)) <- xs] + f _ = error "GHC.Util.HsExpr.replaceBranches: unexpected XMatch" g :: [LMatch GhcPs (LHsExpr GhcPs)] -> [LHsExpr GhcPs] -> [LMatch GhcPs (LHsExpr GhcPs)] - g (LL s1 (Match _ CaseAlt a (GRHSs _ ns b)) : rest) xs = - cL s1 (Match noExt CaseAlt a (GRHSs noExt [cL a (GRHS noExt gs x) | (LL a (GRHS _ gs _), x) <- zip ns as] b)) : g rest bs + g (L s1 (Match _ CaseAlt a (GRHSs _ ns b)) : rest) xs = + cL s1 (Match noExtField CaseAlt a (GRHSs noExtField [cL a (GRHS noExtField gs x) | (L a (GRHS _ gs _), x) <- zip ns as] b)) : g rest bs where (as, bs) = splitAt (length ns) xs g [] [] = [] g _ _ = error "GHC.Util.HsExpr.replaceBranches': internal invariant failed, lists are of differing lengths" -replaceBranches' x = ([], \[] -> x) +replaceBranches x = ([], \[] -> x) --- Like needBracket', but with a special case for 'a . b . b', which was +-- Like needBracket, but with a special case for 'a . b . b', which was -- removed from haskell-src-exts-util-0.2.2. -needBracketOld' :: Int -> LHsExpr GhcPs -> LHsExpr GhcPs -> Bool -needBracketOld' i parent child +needBracketOld :: Int -> LHsExpr GhcPs -> LHsExpr GhcPs -> Bool +needBracketOld i parent child | isDotApp parent, isDotApp child, i == 2 = False - | otherwise = needBracket' i parent child + | otherwise = needBracket i parent child -transformBracketOld' :: (LHsExpr GhcPs -> Maybe (LHsExpr GhcPs)) -> LHsExpr GhcPs -> (LHsExpr GhcPs, LHsExpr GhcPs) -transformBracketOld' op = first snd . g +transformBracketOld :: (LHsExpr GhcPs -> Maybe (LHsExpr GhcPs)) -> LHsExpr GhcPs -> (LHsExpr GhcPs, LHsExpr GhcPs) +transformBracketOld op = first snd . g where - g = first f . descendBracketOld' g + g = first f . descendBracketOld g f x = maybe (False, x) (True, ) (op x) -- Descend, and if something changes then add/remove brackets -- appropriately. Returns (suggested replacement, refactor template). -- Whenever a bracket is added to the suggested replacement, a -- corresponding bracket is added to the refactor template. -descendBracketOld' :: (LHsExpr GhcPs -> ((Bool, LHsExpr GhcPs), LHsExpr GhcPs)) +descendBracketOld :: (LHsExpr GhcPs -> ((Bool, LHsExpr GhcPs), LHsExpr GhcPs)) -> LHsExpr GhcPs -> (LHsExpr GhcPs, LHsExpr GhcPs) -descendBracketOld' op x = (descendIndex' g1 x, descendIndex' g2 x) +descendBracketOld op x = (descendIndex g1 x, descendIndex g2 x) where g i y = if a then (f1 i b z, f2 i b z) else (b, z) where ((a, b), z) = op y @@ -240,32 +291,21 @@ g1 = (fst .) . g g2 = (snd .) . g - f i (LL _ (HsPar _ y)) z | not $ needBracketOld' i x y = (y, z) - f i y z | needBracketOld' i x y = (addParen' y, addParen' z) - f _ y z = (y, z) + f i (L _ (HsPar _ y)) z + | not $ needBracketOld i x y = (y, z) + f i y z + | needBracketOld i x y = (addParen y, addParen z) + -- https://github.com/mpickering/apply-refact/issues/7 + | isOp y = (y, addParen z) + f _ y z = (y, z) f1 = ((fst .) .) . f f2 = ((snd .) .) . f -reduce' :: LHsExpr GhcPs -> LHsExpr GhcPs -reduce' = fromParen' . transform reduce1' - -reduce1' :: LHsExpr GhcPs -> LHsExpr GhcPs -reduce1' (LL loc (HsApp _ len (LL _ (HsLit _ (HsString _ xs))))) - | varToStr len == "length" = cL loc $ HsLit noExt (HsInt noExt (IL NoSourceText False n)) - where n = fromIntegral $ length (unpackFS xs) -reduce1' (LL loc (HsApp _ len (LL _ (ExplicitList _ _ xs)))) - | varToStr len == "length" = cL loc $ HsLit noExt (HsInt noExt (IL NoSourceText False n)) - where n = fromIntegral $ length xs -reduce1' (view' -> App2' op (LL _ (HsLit _ x)) (LL _ (HsLit _ y))) | varToStr op == "==" = strToVar (show (astEq x y)) -reduce1' (view' -> App2' op (LL _ (HsLit _ (HsInt _ x))) (LL _ (HsLit _ (HsInt _ y)))) | varToStr op == ">=" = strToVar $ show (x >= y) -reduce1' (view' -> App2' op x y) - | varToStr op == "&&" && varToStr x == "True" = y - | varToStr op == "&&" && varToStr x == "False" = x -reduce1' (LL _ (HsPar _ x)) | isAtom' x = x -reduce1' x = x - - -fromParen1' :: LHsExpr GhcPs -> LHsExpr GhcPs -fromParen1' (LL _ (HsPar _ x)) = x -fromParen1' x = x + isOp = \case + L _ (HsVar _ (L _ name)) -> isSymbolRdrName name + _ -> False + +fromParen1 :: LHsExpr GhcPs -> LHsExpr GhcPs +fromParen1 (L _ (HsPar _ x)) = x +fromParen1 x = x diff -Nru hlint-2.2.11/src/GHC/Util/HsType.hs hlint-3.1.6/src/GHC/Util/HsType.hs --- hlint-2.2.11/src/GHC/Util/HsType.hs 2020-01-09 16:49:48.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/HsType.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ - -module GHC.Util.HsType ( - Brackets'(..) - , fromTyParen' - , isTyQuasiQuote' - , isUnboxedTuple' - ) where - -import HsSyn -import SrcLoc - -import GHC.Util.Brackets - -fromTyParen' :: LHsType GhcPs -> LHsType GhcPs -fromTyParen' (LL _ (HsParTy _ x)) = x -fromTyParen' x = x - -isTyQuasiQuote' :: LHsType GhcPs -> Bool -isTyQuasiQuote' (LL _ (HsSpliceTy _ HsQuasiQuote{})) = True; isTyQuasiQuote' _ = False - -isUnboxedTuple' :: HsTupleSort -> Bool -isUnboxedTuple' HsUnboxedTuple = True -isUnboxedTuple' _ = False diff -Nru hlint-2.2.11/src/GHC/Util/LanguageExtensions/Type.hs hlint-3.1.6/src/GHC/Util/LanguageExtensions/Type.hs --- hlint-2.2.11/src/GHC/Util/LanguageExtensions/Type.hs 2019-09-24 17:55:46.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/LanguageExtensions/Type.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ - -module GHC.Util.LanguageExtensions.Type ( - readExtension -) where - -import GHC.LanguageExtensions.Type - -import qualified Data.Map.Strict as Map - --- | Parse a GHC extension -readExtension :: String -> Maybe Extension -readExtension x = Map.lookup x exts - where exts = Map.fromList [(show x, x) | x <- [Cpp .. StarIsType]] diff -Nru hlint-2.2.11/src/GHC/Util/Module.hs hlint-3.1.6/src/GHC/Util/Module.hs --- hlint-2.2.11/src/GHC/Util/Module.hs 2019-11-27 22:26:42.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/Module.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ - -module GHC.Util.Module (modName, fromModuleName') where - -import HsSyn -import Module -import SrcLoc - -modName :: Located (HsModule GhcPs) -> String -modName (LL _ HsModule {hsmodName=Nothing}) = "Main" -modName (LL _ HsModule {hsmodName=Just (L _ n)}) = moduleNameString n -modName _ = "" -- {-# COMPLETE LL #-} - -fromModuleName' :: Located ModuleName -> String -fromModuleName' (LL _ n) = moduleNameString n -fromModuleName' _ = "" -- {# COMPLETE LL #} diff -Nru hlint-2.2.11/src/GHC/Util/Outputable.hs hlint-3.1.6/src/GHC/Util/Outputable.hs --- hlint-2.2.11/src/GHC/Util/Outputable.hs 2019-09-24 17:55:46.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/Outputable.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ - -module GHC.Util.Outputable (unsafePrettyPrint) where - -import Outputable - --- \"Unsafe\" in this case means that it uses the following --- 'DynFlags' for printing - --- This could lead to the issues documented --- there, but it also might not be a problem for our use case. TODO: --- Decide whether this really is unsafe, and if it is, what needs to --- be done to make it safe. -unsafePrettyPrint :: (Outputable.Outputable a) => a -> String -unsafePrettyPrint = Outputable.showSDocUnsafe . Outputable.ppr diff -Nru hlint-2.2.11/src/GHC/Util/Pat.hs hlint-3.1.6/src/GHC/Util/Pat.hs --- hlint-2.2.11/src/GHC/Util/Pat.hs 2020-01-09 16:49:48.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/Pat.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,58 +0,0 @@ -{-# LANGUAGE MultiParamTypeClasses , FlexibleInstances, FlexibleContexts #-} - -module GHC.Util.Pat ( - strToPat', patToStr' - , Brackets'(..) - , fromPChar', isPFieldWildcard', hasPFieldsDotDot', isPWildCard' - , isPFieldPun', isPatTypeSig', isPBangPat', isPViewPat' - ) where - -import HsSyn -import SrcLoc -import TysWiredIn -import FastString -import RdrName - -import GHC.Util.Brackets - -patToStr' :: Pat GhcPs -> String -patToStr' (LL _ (ConPatIn (L _ x) (PrefixCon []))) | x == true_RDR = "True" -patToStr' (LL _ (ConPatIn (L _ x) (PrefixCon []))) | x == false_RDR = "False" -patToStr' (LL _ (ConPatIn (L _ x) (PrefixCon []))) | x == nameRdrName nilDataConName = "[]" -patToStr' _ = "" - -strToPat' :: String -> Pat GhcPs -strToPat' z - | z == "True" = ConPatIn (noLoc true_RDR) (PrefixCon []) - | z == "False" = ConPatIn (noLoc false_RDR) (PrefixCon []) - | z == "[]" = ConPatIn (noLoc $ nameRdrName nilDataConName) (PrefixCon []) - | otherwise = VarPat noExt (noLoc $ mkVarUnqual (fsLit z)) - -fromPChar' :: Pat GhcPs -> Maybe Char -fromPChar' (LL _ (LitPat _ (HsChar _ x))) = Just x -fromPChar' _ = Nothing - --- Contains a '..' as in 'Foo{..}' -hasPFieldsDotDot' :: HsRecFields GhcPs (Pat GhcPs) -> Bool -hasPFieldsDotDot' HsRecFields {rec_dotdot=Just _} = True -hasPFieldsDotDot' _ = False -- {-# COMPLETE LL #-} - --- Field has a '_' as in '{foo=_} or is punned e.g. '{foo}'. -isPFieldWildcard' :: LHsRecField GhcPs (Pat GhcPs) -> Bool -isPFieldWildcard' (LL _ HsRecField {hsRecFieldArg=(LL _ (WildPat _))}) = True -isPFieldWildcard' (LL _ HsRecField {hsRecPun=True}) = True -isPFieldWildcard' (LL _ HsRecField {}) = False -isPFieldWildcard' _ = False -- {-# COMPLETE LL #-} - -isPWildCard' :: Pat GhcPs -> Bool -isPWildCard' (LL _ (WildPat _)) = True -isPWildCard' _ = False - -isPFieldPun' :: LHsRecField GhcPs (Pat GhcPs) -> Bool -isPFieldPun' (LL _ HsRecField {hsRecPun=True}) = True -isPFieldPun' _ = False - -isPatTypeSig', isPBangPat', isPViewPat' :: Pat GhcPs -> Bool -isPatTypeSig' (LL _ SigPat{}) = True; isPatTypeSig' _ = False -isPBangPat' (LL _ BangPat{}) = True; isPBangPat' _ = False -isPViewPat' (LL _ ViewPat{}) = True; isPViewPat' _ = False diff -Nru hlint-2.2.11/src/GHC/Util/RdrName.hs hlint-3.1.6/src/GHC/Util/RdrName.hs --- hlint-2.2.11/src/GHC/Util/RdrName.hs 2019-11-27 22:26:42.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/RdrName.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -module GHC.Util.RdrName (isSpecial', unqual', rdrNameStr',fromQual') where - -import SrcLoc -import Name -import RdrName - -rdrNameStr' :: Located RdrName -> String -rdrNameStr' = occNameString . rdrNameOcc . unLoc - --- Builtin type or data constructors. -isSpecial' :: Located RdrName -> Bool -isSpecial' (L _ (Exact n)) = isDataConName n || isTyConName n -isSpecial' _ = False - --- Coerce qualified names to unqualified (by discarding the --- qualifier). -unqual' :: Located RdrName -> Located RdrName -unqual' (L loc (Qual _ n)) = cL loc $ mkRdrUnqual n -unqual' x = x - -fromQual' :: Located RdrName -> Maybe OccName -fromQual' (L _ (Qual _ x)) = Just x -fromQual' (L _ (Unqual x)) = Just x -fromQual' _ = Nothing diff -Nru hlint-2.2.11/src/GHC/Util/Scope.hs hlint-3.1.6/src/GHC/Util/Scope.hs --- hlint-2.2.11/src/GHC/Util/Scope.hs 2020-01-27 16:04:14.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/Scope.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,12 +1,13 @@ + {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} module GHC.Util.Scope ( - Scope' - ,scopeCreate',scopeImports',scopeMatch',scopeMove' + Scope + ,scopeCreate,scopeMatch,scopeMove,possModules ) where -import HsSyn +import GHC.Hs import SrcLoc import BasicTypes import Module @@ -14,26 +15,26 @@ import RdrName import OccName -import GHC.Util.Module -import GHC.Util.RdrName -import Outputable +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable -import Data.List import Data.List.Extra import Data.Maybe -- A scope is a list of import declarations. -newtype Scope' = Scope' [LImportDecl GhcPs] - deriving (Outputable, Monoid, Semigroup) +newtype Scope = Scope [LImportDecl GhcPs] + deriving (Monoid, Semigroup) + +instance Show Scope where + show (Scope x) = unsafePrettyPrint x --- Create a 'Scope' from a module's import declarations. -scopeCreate' :: HsModule GhcPs -> Scope' -scopeCreate' xs = Scope' $ [prelude | not $ any isPrelude res] ++ res +-- Create a 'Scope from a module's import declarations. +scopeCreate :: HsModule GhcPs -> Scope +scopeCreate xs = Scope $ [prelude | not $ any isPrelude res] ++ res where -- Package qualifier of an import declaration. pkg :: LImportDecl GhcPs -> Maybe StringLiteral - pkg (LL _ x) = ideclPkgQual x - pkg _ = Nothing -- {-# COMPLETE LL #-} + pkg (L _ x) = ideclPkgQual x -- The import declaraions contained by the module 'xs'. res :: [LImportDecl GhcPs] @@ -45,62 +46,57 @@ -- Predicate to test for a 'Prelude' import declaration. isPrelude :: LImportDecl GhcPs -> Bool - isPrelude (LL _ x) = fromModuleName' (ideclName x) == "Prelude" - isPrelude _ = False -- {-# COMPLETE LL #-} - --- Access the imports in scope 'x'. -scopeImports' :: Scope' -> [LImportDecl GhcPs] -scopeImports' (Scope' x) = x + isPrelude (L _ x) = moduleNameString (unLoc (ideclName x)) == "Prelude" -- Test if two names in two scopes may be referring to the same -- thing. This is the case if the names are equal and (1) denote a -- builtin type or data constructor or (2) the intersection of the -- candidate modules where the two names arise is non-empty. -scopeMatch' :: (Scope', Located RdrName) -> (Scope', Located RdrName) -> Bool -scopeMatch' (a, x) (b, y) - | isSpecial' x && isSpecial' y = rdrNameStr' x == rdrNameStr' y - | isSpecial' x || isSpecial' y = False +scopeMatch :: (Scope, Located RdrName) -> (Scope, Located RdrName) -> Bool +scopeMatch (a, x) (b, y) + | isSpecial x && isSpecial y = rdrNameStr x == rdrNameStr y + | isSpecial x || isSpecial y = False | otherwise = - rdrNameStr' (unqual' x) == rdrNameStr' (unqual' y) && not (null $ possModules' a x `intersect` possModules' b y) + rdrNameStr (unqual x) == rdrNameStr (unqual y) && not (possModules a x `disjointOrd` possModules b y) -- Given a name in a scope, and a new scope, create a name for the new -- scope that will refer to the same thing. If the resulting name is -- ambiguous, pick a plausible candidate. -scopeMove' :: (Scope', Located RdrName) -> Scope' -> Located RdrName -scopeMove' (a, x@(fromQual' -> Just name)) (Scope' b) = case imps of - [] -> head $ real ++ [x] - imp:_ | all ideclQualified imps -> noLoc $ mkRdrQual (unLoc . fromMaybe (ideclName imp) $ firstJust ideclAs imps) name - | otherwise -> unqual' x +scopeMove :: (Scope, Located RdrName) -> Scope -> Located RdrName +scopeMove (a, x@(fromQual -> Just name)) (Scope b) = case imps of + [] -> headDef x real + imp:_ | all (\x -> ideclQualified x /= NotQualified) imps -> noLoc $ mkRdrQual (unLoc . fromMaybe (ideclName imp) $ firstJust ideclAs imps) name + | otherwise -> unqual x where real :: [Located RdrName] - real = [noLoc $ mkRdrQual (mkModuleName m) name | m <- possModules' a x] + real = [noLoc $ mkRdrQual m name | m <- possModules a x] imps :: [ImportDecl GhcPs] - imps = [unLoc i | r <- real, i <- b, possImport' i r] -scopeMove' (_, x) _ = x + imps = [unLoc i | r <- real, i <- b, possImport i r] +scopeMove (_, x) _ = x -- Calculate which modules a name could possibly lie in. If 'x' is -- qualified but no imported element matches it, assume the user just -- lacks an import. -possModules' :: Scope' -> Located RdrName -> [String] -possModules' (Scope' is) x = f x +possModules :: Scope -> Located RdrName -> [ModuleName] +possModules (Scope is) x = f x where - res :: [String] - res = [fromModuleName' $ ideclName (unLoc i) | i <- is, possImport' i x] + res :: [ModuleName] + res = [unLoc $ ideclName $ unLoc i | i <- is, possImport i x] - f :: Located RdrName -> [String] - f n | isSpecial' n = [""] - f (L _ (Qual mod _)) = [moduleNameString mod | null res] ++ res + f :: Located RdrName -> [ModuleName] + f n | isSpecial n = [mkModuleName ""] + f (L _ (Qual mod _)) = [mod | null res] ++ res f _ = res -- Determine if 'x' could possibly lie in the module named by the -- import declaration 'i'. -possImport' :: LImportDecl GhcPs -> Located RdrName -> Bool -possImport' i n | isSpecial' n = False -possImport' (LL _ i) (L _ (Qual mod x)) = - moduleNameString mod `elem` map fromModuleName' ms && possImport' (noLoc i{ideclQualified=False}) (noLoc $ mkRdrUnqual x) - where ms = ideclName i : maybeToList (ideclAs i) -possImport' (LL _ i) (L _ (Unqual x)) = not (ideclQualified i) && maybe True f (ideclHiding i) +possImport :: LImportDecl GhcPs -> Located RdrName -> Bool +possImport i n | isSpecial n = False +possImport (L _ i) (L _ (Qual mod x)) = + mod `elem` ms && possImport (noLoc i{ideclQualified=NotQualified}) (noLoc $ mkRdrUnqual x) + where ms = map unLoc $ ideclName i : maybeToList (ideclAs i) +possImport (L _ i) (L _ (Unqual x)) = ideclQualified i == NotQualified && maybe True f (ideclHiding i) where f :: (Bool, Located [LIE GhcPs]) -> Bool f (hide, L _ xs) = @@ -122,4 +118,4 @@ unwrapName :: LIEWrappedName RdrName -> String unwrapName x = occNameString (rdrNameOcc $ ieWrappedName (unLoc x)) -possImport' _ _ = False -- {-# COMPLETE LL #-} +possImport _ _ = False diff -Nru hlint-2.2.11/src/GHC/Util/SrcLoc.hs hlint-3.1.6/src/GHC/Util/SrcLoc.hs --- hlint-2.2.11/src/GHC/Util/SrcLoc.hs 2019-09-24 17:55:46.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/SrcLoc.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,7 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} module GHC.Util.SrcLoc ( - stripLocs' + stripLocs , SrcSpanD(..) ) where @@ -10,12 +10,12 @@ import Data.Default import Data.Data -import Data.Generics.Uniplate.Data +import Data.Generics.Uniplate.DataOnly -- 'stripLocs x' is 'x' with all contained source locs replaced by -- 'noSrcSpan'. -stripLocs' :: (Data from, HasSrcSpan from) => from -> from -stripLocs' = transformBi (const noSrcSpan) +stripLocs :: (Data from, HasSrcSpan from) => from -> from +stripLocs = transformBi (const noSrcSpan) -- 'Duplicates.hs' requires 'SrcSpan' be in 'Default'. newtype SrcSpanD = SrcSpanD SrcSpan diff -Nru hlint-2.2.11/src/GHC/Util/Unify.hs hlint-3.1.6/src/GHC/Util/Unify.hs --- hlint-2.2.11/src/GHC/Util/Unify.hs 2020-02-09 21:16:43.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/Unify.hs 2020-06-24 20:49:58.000000000 +0000 @@ -1,32 +1,32 @@ -{-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts, ScopedTypeVariables #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts, ScopedTypeVariables, TupleSections #-} +{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveFunctor #-} module GHC.Util.Unify( - Subst', fromSubst', - validSubst', substitute', - unifyExp' + Subst, fromSubst, + validSubst, removeParens, substitute, + unifyExp ) where +import Control.Applicative import Control.Monad -import Data.Generics.Uniplate.Operations +import Data.Generics.Uniplate.DataOnly import Data.Char -import Data.List.Extra import Data.Data -import Data.Tuple.Extra +import Data.List.Extra import Util -import HsSyn -import SrcLoc as GHC +import GHC.Hs +import SrcLoc import Outputable hiding ((<>)) import RdrName -import OccName +import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr -import GHC.Util.Outputable +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader import GHC.Util.HsExpr -import GHC.Util.Pat -import GHC.Util.RdrName import GHC.Util.View +import FastString isUnifyVar :: String -> Bool isUnifyVar [x] = x == '?' || isAlpha x @@ -38,148 +38,208 @@ -- A list of substitutions. A key may be duplicated, you need to call -- 'check' to ensure the substitution is valid. -newtype Subst' a = Subst' [(String, a)] - deriving (Semigroup, Monoid) +newtype Subst a = Subst [(String, a)] + deriving (Semigroup, Monoid, Functor) -- Unpack the substitution. -fromSubst' :: Subst' a -> [(String, a)] -fromSubst' (Subst' xs) = xs +fromSubst :: Subst a -> [(String, a)] +fromSubst (Subst xs) = xs -instance Functor Subst' where - fmap f (Subst' xs) = Subst' $ map (second f) xs -- Interesting. - -instance Outputable a => Show (Subst' a) where - show (Subst' xs) = unlines [a ++ " = " ++ unsafePrettyPrint b | (a,b) <- xs] +instance Outputable a => Show (Subst a) where + show (Subst xs) = unlines [a ++ " = " ++ unsafePrettyPrint b | (a,b) <- xs] -- Check the unification is valid and simplify it. -validSubst' :: (a -> a -> Bool) -> Subst' a -> Maybe (Subst' a) -validSubst' eq = fmap Subst' . mapM f . groupSort . fromSubst' +validSubst :: (a -> a -> Bool) -> Subst a -> Maybe (Subst a) +validSubst eq = fmap Subst . mapM f . groupSort . fromSubst where f (x, y : ys) | all (eq y) ys = Just (x, y) f _ = Nothing +-- Remove unnecessary brackets from a Subst. The first argument is a list of unification variables +-- for which brackets should be removed from their substitutions. +removeParens :: [String] -> Subst (LHsExpr GhcPs) -> Subst (LHsExpr GhcPs) +removeParens noParens (Subst xs) = Subst $ + map (\(x, y) -> if x `elem` noParens then (x, fromParen y) else (x, y)) xs + -- Peform a substition. -- Returns (suggested replacement, refactor template), both with brackets added -- as needed. -- Example: (traverse foo (bar baz), traverse f (x)) -substitute' :: Subst' (LHsExpr GhcPs) -> LHsExpr GhcPs -> (LHsExpr GhcPs, LHsExpr GhcPs) -substitute' (Subst' bind) = transformBracketOld' exp . transformBi pat . transformBi typ +substitute :: Subst (LHsExpr GhcPs) -> LHsExpr GhcPs -> (LHsExpr GhcPs, LHsExpr GhcPs) +substitute (Subst bind) = transformBracketOld exp . transformBi pat . transformBi typ where exp :: LHsExpr GhcPs -> Maybe (LHsExpr GhcPs) -- Variables. - exp (LL _ (HsVar _ x)) = lookup (rdrNameStr' x) bind + exp (L _ (HsVar _ x)) = lookup (rdrNameStr x) bind -- Operator applications. - exp (LL loc (OpApp _ lhs (LL _ (HsVar _ x)) rhs)) - | Just y <- lookup (rdrNameStr' x) bind = Just (cL loc (OpApp noExt lhs y rhs)) + exp (L loc (OpApp _ lhs (L _ (HsVar _ x)) rhs)) + | Just y <- lookup (rdrNameStr x) bind = Just (cL loc (OpApp noExtField lhs y rhs)) -- Left sections. - exp (LL loc (SectionL _ exp (LL _ (HsVar _ x)))) - | Just y <- lookup (rdrNameStr' x) bind = Just (cL loc (SectionL noExt exp y)) + exp (L loc (SectionL _ exp (L _ (HsVar _ x)))) + | Just y <- lookup (rdrNameStr x) bind = Just (cL loc (SectionL noExtField exp y)) -- Right sections. - exp (LL loc (SectionR _ (LL _ (HsVar _ x)) exp)) - | Just y <- lookup (rdrNameStr' x) bind = Just (cL loc (SectionR noExt y exp)) + exp (L loc (SectionR _ (L _ (HsVar _ x)) exp)) + | Just y <- lookup (rdrNameStr x) bind = Just (cL loc (SectionR noExtField y exp)) exp _ = Nothing pat :: LPat GhcPs -> LPat GhcPs -- Pattern variables. - pat (LL _ (VarPat _ x)) - | Just y@(LL _ HsVar{}) <- lookup (rdrNameStr' x) bind = strToPat' (varToStr y) + pat (L _ (VarPat _ x)) + | Just y@(L _ HsVar{}) <- lookup (rdrNameStr x) bind = strToPat $ varToStr y pat x = x :: LPat GhcPs typ :: LHsType GhcPs -> LHsType GhcPs -- Type variables. - typ (LL _ (HsTyVar _ _ x)) - | Just (LL _ (HsAppType _ _ (HsWC _ y))) <- lookup (rdrNameStr' x) bind = y + typ (L _ (HsTyVar _ _ x)) + | Just (L _ (HsAppType _ _ (HsWC _ y))) <- lookup (rdrNameStr x) bind = y typ x = x :: LHsType GhcPs --------------------------------------------------------------------- -- UNIFICATION -type NameMatch' = Located RdrName -> Located RdrName -> Bool +type NameMatch = Located RdrName -> Located RdrName -> Bool -- | Unification, obeys the property that if @unify a b = s@, then -- @substitute s a = b@. -unify' :: Data a => NameMatch' -> Bool -> a -> a -> Maybe (Subst' (LHsExpr GhcPs)) +unify' :: Data a => NameMatch -> Bool -> a -> a -> Maybe (Subst (LHsExpr GhcPs)) unify' nm root x y | Just (x, y) <- cast (x, y) = unifyExp' nm root x y | Just (x, y) <- cast (x, y) = unifyPat' nm x y | Just (x, y) <- cast (x, y) = unifyType' nm x y - | Just (x :: GHC.SrcSpan) <- cast x = Just mempty + | Just (x, y) <- cast (x, y) = if (x :: FastString) == y then Just mempty else Nothing + | Just (x :: SrcSpan) <- cast x = Just mempty | otherwise = unifyDef' nm x y -unifyDef' :: Data a => NameMatch' -> a -> a -> Maybe (Subst' (LHsExpr GhcPs)) +unifyDef' :: Data a => NameMatch -> a -> a -> Maybe (Subst (LHsExpr GhcPs)) unifyDef' nm x y = fmap mconcat . sequence =<< gzip (unify' nm False) x y +unifyComposed' :: NameMatch + -> LHsExpr GhcPs + -> LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs + -> Maybe (Subst (LHsExpr GhcPs), Maybe (LHsExpr GhcPs)) +unifyComposed' nm x1 y11 dot y12 = + ((, Just y11) <$> unifyExp' nm False x1 y12) + <|> case y12 of + (L _ (OpApp _ y121 dot' y122)) | isDot dot' -> + unifyComposed' nm x1 (noLoc (OpApp noExtField y11 dot y121)) dot' y122 + _ -> Nothing + +-- unifyExp handles the cases where both x and y are HsApp, or y is OpApp. Otherwise, +-- delegate to unifyExp'. These are the cases where we potentially need to call +-- unifyComposed' to handle left composition. +-- +-- y is allowed to partially match x (the lhs of the hint), if y is a function application where +-- the function is a composition of functions. In this case the second component of the result is +-- the unmatched part of y, which will be attached to the rhs of the hint after substitution. +-- +-- Example: +-- x = head (drop n x) +-- y = foo . bar . baz . head $ drop 2 xs +-- result = (Subst [(n, 2), (x, xs)], Just (foo . bar . baz)) +unifyExp :: NameMatch -> Bool -> LHsExpr GhcPs -> LHsExpr GhcPs -> Maybe (Subst (LHsExpr GhcPs), Maybe (LHsExpr GhcPs)) +-- Match wildcard operators. +unifyExp nm root (L _ (OpApp _ lhs1 (L _ (HsVar _ (rdrNameStr -> v))) rhs1)) + (L _ (OpApp _ lhs2 (L _ (HsVar _ (rdrNameStr -> op2))) rhs2)) + | isUnifyVar v = + (, Nothing) . (Subst [(v, strToVar op2)] <>) <$> + liftA2 (<>) (unifyExp' nm False lhs1 lhs2) (unifyExp' nm False rhs1 rhs2) + +-- Options: match directly, and expand through '.' +unifyExp nm root x@(L _ (HsApp _ x1 x2)) (L _ (HsApp _ y1 y2)) = + ((, Nothing) <$> liftA2 (<>) (unifyExp' nm False x1 y1) (unifyExp' nm False x2 y2)) <|> unifyComposed + where + -- Unify a function application where the function is a composition of functions. + unifyComposed + | (L _ (OpApp _ y11 dot y12)) <- fromParen y1, isDot dot = + if not root then + -- Attempt #1: rewrite '(fun1 . fun2) arg' as 'fun1 (fun2 arg)', and unify it with 'x'. + -- The guard ensures that you don't get duplicate matches because the matching engine + -- auto-generates hints in dot-form. + (, Nothing) <$> unifyExp' nm root x (noLoc (HsApp noExtField y11 (noLoc (HsApp noExtField y12 y2)))) + else do + -- Attempt #2: rewrite '(fun1 . fun2 ... funn) arg' as 'fun1 $ (fun2 ... funn) arg', + -- 'fun1 . fun2 $ (fun3 ... funn) arg', 'fun1 . fun2 . fun3 $ (fun4 ... funn) arg', + -- and so on, unify the rhs of '$' with 'x', and store the lhs of '$' into 'extra'. + -- You can only add to extra if you are at the root (otherwise 'extra' has nowhere to go). + rhs <- unifyExp' nm False x2 y2 + (lhs, extra) <- unifyComposed' nm x1 y11 dot y12 + pure (lhs <> rhs, extra) + | otherwise = Nothing + +-- Options: match directly, then expand through '$', then desugar infix. +unifyExp nm root x (L _ (OpApp _ lhs2 op2@(L _ (HsVar _ op2')) rhs2)) + | (L _ (OpApp _ lhs1 op1@(L _ (HsVar _ op1')) rhs1)) <- x = + guard (nm op1' op2') >> (, Nothing) <$> liftA2 (<>) (unifyExp' nm False lhs1 lhs2) (unifyExp' nm False rhs1 rhs2) + | isDol op2 = unifyExp nm root x $ noLoc (HsApp noExtField lhs2 rhs2) + | otherwise = unifyExp nm root x $ noLoc (HsApp noExtField (noLoc (HsApp noExtField op2 (addPar lhs2))) (addPar rhs2)) + where + -- add parens around when desugaring the expression, if necessary + addPar :: LHsExpr GhcPs -> LHsExpr GhcPs + addPar x = if isAtom x then x else addParen x + +unifyExp nm root x y = (, Nothing) <$> unifyExp' nm root x y + +-- | If we "throw away" the extra than we have no where to put it, and the substitution is wrong +noExtra :: Maybe (Subst (LHsExpr GhcPs), Maybe (LHsExpr GhcPs)) -> Maybe (Subst (LHsExpr GhcPs)) +noExtra (Just (x, Nothing)) = Just x +noExtra _ = Nothing + -- App/InfixApp are analysed specially for performance reasons. If -- 'root = True', this is the outside of the expr. Do not expand out a -- dot at the root, since otherwise you get two matches because of -- 'readRule' (Bug #570). -unifyExp' :: NameMatch' -> Bool -> LHsExpr GhcPs -> LHsExpr GhcPs -> Maybe (Subst' (LHsExpr GhcPs) ) +unifyExp' :: NameMatch -> Bool -> LHsExpr GhcPs -> LHsExpr GhcPs -> Maybe (Subst (LHsExpr GhcPs)) -- Brackets are not added when expanding '$' in user code, so tolerate -- them in the match even if they aren't in the user code. -unifyExp' nm root x y | not root, isPar x, not $ isPar y = unifyExp' nm root (fromParen' x) y --- Don't subsitute for type apps, since no one writes rules imaginging +unifyExp' nm root x y | not root, isPar x, not $ isPar y = unifyExp' nm root (fromParen x) y +-- Don't subsitute for type apps, since no one writes rules imagining -- they exist. -unifyExp' nm root (LL _ (HsVar _ (rdrNameStr' -> v))) y | isUnifyVar v, not $ isTypeApp y = Just $ Subst' [(v, y)] -unifyExp' nm root (LL _ (HsVar _ x)) (LL _ (HsVar _ y)) | nm x y = Just mempty +unifyExp' nm root (L _ (HsVar _ (rdrNameStr -> v))) y | isUnifyVar v, not $ isTypeApp y = Just $ Subst [(v, y)] +unifyExp' nm root (L _ (HsVar _ x)) (L _ (HsVar _ y)) | nm x y = Just mempty --- Match wildcard operators. -unifyExp' nm root (LL _ (OpApp _ lhs1 (LL _ (HsVar _ (rdrNameStr' -> v))) rhs1)) - (LL _ (OpApp _ lhs2 (LL _ (HsVar _ (rdrNameStr' -> op2))) rhs2)) - | isUnifyVar v = - (Subst' [(v, strToVar op2)] <>) <$> - liftM2 (<>) (unifyExp' nm False lhs1 lhs2) (unifyExp' nm False rhs1 rhs2) -unifyExp' nm root (LL _ (SectionL _ exp1 (LL _ (HsVar _ (rdrNameStr' -> v))))) - (LL _ (SectionL _ exp2 (LL _ (HsVar _ (rdrNameStr' -> op2))))) - | isUnifyVar v = (Subst' [(v, strToVar op2)] <>) <$> unifyExp' nm False exp1 exp2 -unifyExp' nm root (LL _ (SectionR _ (LL _ (HsVar _ (rdrNameStr' -> v))) exp1)) - (LL _ (SectionR _ (LL _ (HsVar _ (rdrNameStr' -> op2))) exp2)) - | isUnifyVar v = (Subst' [(v, strToVar op2)] <>) <$> unifyExp' nm False exp1 exp2 +unifyExp' nm root x@(L _ (OpApp _ lhs1 (L _ (HsVar _ (rdrNameStr -> v))) rhs1)) + y@(L _ (OpApp _ lhs2 (L _ (HsVar _ op2)) rhs2)) = + noExtra $ unifyExp nm root x y +unifyExp' nm root (L _ (SectionL _ exp1 (L _ (HsVar _ (rdrNameStr -> v))))) + (L _ (SectionL _ exp2 (L _ (HsVar _ (rdrNameStr -> op2))))) + | isUnifyVar v = (Subst [(v, strToVar op2)] <>) <$> unifyExp' nm False exp1 exp2 +unifyExp' nm root (L _ (SectionR _ (L _ (HsVar _ (rdrNameStr -> v))) exp1)) + (L _ (SectionR _ (L _ (HsVar _ (rdrNameStr -> op2))) exp2)) + | isUnifyVar v = (Subst [(v, strToVar op2)] <>) <$> unifyExp' nm False exp1 exp2 --- Options: match directly, and expand through '.' -unifyExp' nm root x@(LL _ (HsApp _ x1 x2)) (LL _ (HsApp _ y1 y2)) = - liftM2 (<>) (unifyExp' nm False x1 y1) (unifyExp' nm False x2 y2) `mplus` - (do guard $ not root - -- Don't expand '.' f at the root, otherwise you can get - -- duplicate matches because the matching engine - -- auto-generates hints in dot-form. - (LL _ (OpApp _ y11 dot y12)) <- return $ fromParen' y1 - guard $ isDot dot - unifyExp' nm root x (noLoc (HsApp noExt y11 (noLoc (HsApp noExt y12 y2)))) - ) +unifyExp' nm root x@(L _ (HsApp _ x1 x2)) y@(L _ (HsApp _ y1 y2)) = + noExtra $ unifyExp nm root x y --- Options: match directly, then expand through '$', then desugar infix. -unifyExp' nm root x (LL _ (OpApp _ lhs2 op2@(LL _ (HsVar _ op2')) rhs2)) - | (LL _ (OpApp _ lhs1 op1@(LL _ (HsVar _ op1')) rhs1)) <- x = guard (nm op1' op2') >> liftM2 (<>) (unifyExp' nm False lhs1 lhs2) (unifyExp' nm False rhs1 rhs2) - | isDol op2 = unifyExp' nm root x $ noLoc (HsApp noExt lhs2 rhs2) - | otherwise = unifyExp' nm root x $ noLoc (HsApp noExt (noLoc (HsApp noExt op2 lhs2)) rhs2) +unifyExp' nm root x y@(L _ (OpApp _ lhs2 op2@(L _ (HsVar _ op2')) rhs2)) = + noExtra $ unifyExp nm root x y unifyExp' nm root x y | isOther x, isOther y = unifyDef' nm x y where -- Types that are not already handled in unify. {-# INLINE isOther #-} isOther :: LHsExpr GhcPs -> Bool - isOther (LL _ HsVar{}) = False - isOther (LL _ HsApp{}) = False - isOther (LL _ OpApp{}) = False + isOther (L _ HsVar{}) = False + isOther (L _ HsApp{}) = False + isOther (L _ OpApp{}) = False isOther _ = True unifyExp' _ _ _ _ = Nothing -unifyPat' :: NameMatch' -> LPat GhcPs -> LPat GhcPs -> Maybe (Subst' (LHsExpr GhcPs)) -unifyPat' nm (LL _ (VarPat _ x)) (LL _ (VarPat _ y)) = - Just $ Subst' [(rdrNameStr' x, strToVar(rdrNameStr' y))] -unifyPat' nm (LL _ (VarPat _ x)) (LL _ (WildPat _)) = - let s = rdrNameStr' x in Just $ Subst' [(s, strToVar("_" ++ s))] -unifyPat' nm (LL _ (ConPatIn x _)) (LL _ (ConPatIn y _)) | rdrNameStr' x /= rdrNameStr' y = +unifyPat' :: NameMatch -> LPat GhcPs -> LPat GhcPs -> Maybe (Subst (LHsExpr GhcPs)) +unifyPat' nm (L _ (VarPat _ x)) (L _ (VarPat _ y)) = + Just $ Subst [(rdrNameStr x, strToVar(rdrNameStr y))] +unifyPat' nm (L _ (VarPat _ x)) (L _ (WildPat _)) = + let s = rdrNameStr x in Just $ Subst [(s, strToVar("_" ++ s))] +unifyPat' nm (L _ (ConPatIn x _)) (L _ (ConPatIn y _)) | rdrNameStr x /= rdrNameStr y = Nothing unifyPat' nm x y = unifyDef' nm x y -unifyType' :: NameMatch' -> LHsType GhcPs -> LHsType GhcPs -> Maybe (Subst' (LHsExpr GhcPs)) -unifyType' nm (LL loc (HsTyVar _ _ x)) y = - let wc = HsWC noExt y :: LHsWcType (NoGhcTc GhcPs) - unused = noLoc (HsVar noExt (noLoc $ mkRdrUnqual (mkVarOcc "__unused__"))) :: LHsExpr GhcPs - appType = cL loc (HsAppType noExt unused wc) :: LHsExpr GhcPs - in Just $ Subst' [(rdrNameStr' x, appType)] +unifyType' :: NameMatch -> LHsType GhcPs -> LHsType GhcPs -> Maybe (Subst (LHsExpr GhcPs)) +unifyType' nm (L loc (HsTyVar _ _ x)) y = + let wc = HsWC noExtField y :: LHsWcType (NoGhcTc GhcPs) + unused = strToVar "__unused__" :: LHsExpr GhcPs + appType = cL loc (HsAppType noExtField unused wc) :: LHsExpr GhcPs + in Just $ Subst [(rdrNameStr x, appType)] unifyType' nm x y = unifyDef' nm x y diff -Nru hlint-2.2.11/src/GHC/Util/View.hs hlint-3.1.6/src/GHC/Util/View.hs --- hlint-2.2.11/src/GHC/Util/View.hs 2019-11-02 16:50:13.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util/View.hs 2020-05-21 15:48:31.000000000 +0000 @@ -1,55 +1,59 @@ -{-# LANGUAGE ViewPatterns, MultiParamTypeClasses, FlexibleInstances #-} +{-# LANGUAGE ViewPatterns, MultiParamTypeClasses, FlexibleInstances, PatternSynonyms #-} module GHC.Util.View ( - fromParen', fromPParen' - , View'(..) - , Var_'(Var_'), PVar_'(PVar_'), PApp_'(PApp_'), App2'(App2'),LamConst1'(LamConst1') + fromParen + , View(..) + , Var_(Var_), PVar_(PVar_), PApp_(PApp_), App2(App2),LamConst1(LamConst1) + , pattern SimpleLambda ) where -import HsSyn +import GHC.Hs import SrcLoc -import RdrName -import OccName import BasicTypes +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader -fromParen' :: LHsExpr GhcPs -> LHsExpr GhcPs -fromParen' (LL _ (HsPar _ x)) = fromParen' x -fromParen' x = x - -fromPParen' :: Pat GhcPs -> Pat GhcPs -fromPParen' (LL _ (ParPat _ x)) = fromPParen' x -fromPParen' x = x - -class View' a b where - view' :: a -> b - -data Var_' = NoVar_' | Var_' String deriving Eq -data PVar_' = NoPVar_' | PVar_' String -data PApp_' = NoPApp_' | PApp_' String [Pat GhcPs] -data App2' = NoApp2' | App2' (LHsExpr GhcPs) (LHsExpr GhcPs) (LHsExpr GhcPs) -data LamConst1' = NoLamConst1' | LamConst1' (LHsExpr GhcPs) - -instance View' (LHsExpr GhcPs) LamConst1' where - view' (fromParen' -> (LL _ (HsLam _ (MG _ (L _ [LL _ (Match _ LambdaExpr [LL _ WildPat {}] - (GRHSs _ [LL _ (GRHS _ [] x)] (LL _ (EmptyLocalBinds _))))]) FromSource)))) = LamConst1' x - view' _ = NoLamConst1' - -instance View' (LHsExpr GhcPs) Var_' where - view' (fromParen' -> (LL _ (HsVar _ (LL _ (Unqual x))))) = Var_' $ occNameString x - view' _ = NoVar_' - -instance View' (LHsExpr GhcPs) App2' where - view' (fromParen' -> LL _ (OpApp _ lhs op rhs)) = App2' op lhs rhs - view' (fromParen' -> LL _ (HsApp _ (LL _ (HsApp _ f x)) y)) = App2' f x y - view' _ = NoApp2' - -instance View' (Pat GhcPs) PVar_' where - view' (fromPParen' -> LL _ (VarPat _ (L _ x))) = PVar_' $ occNameString (rdrNameOcc x) - view' _ = NoPVar_' - -instance View' (Pat GhcPs) PApp_' where - view' (fromPParen' -> LL _ (ConPatIn (L _ x) (PrefixCon args))) = - PApp_' (occNameString . rdrNameOcc $ x) args - view' (fromPParen' -> LL _ (ConPatIn (L _ x) (InfixCon lhs rhs))) = - PApp_' (occNameString . rdrNameOcc $ x) [lhs, rhs] - view' _ = NoPApp_' +fromParen :: LHsExpr GhcPs -> LHsExpr GhcPs +fromParen (L _ (HsPar _ x)) = fromParen x +fromParen x = x + +fromPParen :: LPat GhcPs -> LPat GhcPs +fromPParen (L _ (ParPat _ x)) = fromPParen x +fromPParen x = x + +class View a b where + view :: a -> b + +data Var_ = NoVar_ | Var_ String deriving Eq +data PVar_ = NoPVar_ | PVar_ String +data PApp_ = NoPApp_ | PApp_ String [LPat GhcPs] +data App2 = NoApp2 | App2 (LHsExpr GhcPs) (LHsExpr GhcPs) (LHsExpr GhcPs) +data LamConst1 = NoLamConst1 | LamConst1 (LHsExpr GhcPs) + +instance View (LHsExpr GhcPs) LamConst1 where + view (fromParen -> (L _ (HsLam _ (MG _ (L _ [L _ (Match _ LambdaExpr [L _ WildPat {}] + (GRHSs _ [L _ (GRHS _ [] x)] (L _ (EmptyLocalBinds _))))]) FromSource)))) = LamConst1 x + view _ = NoLamConst1 + +instance View (LHsExpr GhcPs) Var_ where + view (fromParen -> (L _ (HsVar _ (rdrNameStr -> x)))) = Var_ x + view _ = NoVar_ + +instance View (LHsExpr GhcPs) App2 where + view (fromParen -> L _ (OpApp _ lhs op rhs)) = App2 op lhs rhs + view (fromParen -> L _ (HsApp _ (L _ (HsApp _ f x)) y)) = App2 f x y + view _ = NoApp2 + +instance View (Located (Pat GhcPs)) PVar_ where + view (fromPParen -> L _ (VarPat _ (L _ x))) = PVar_ $ occNameStr x + view _ = NoPVar_ + +instance View (Located (Pat GhcPs)) PApp_ where + view (fromPParen -> L _ (ConPatIn (L _ x) (PrefixCon args))) = + PApp_ (occNameStr x) args + view (fromPParen -> L _ (ConPatIn (L _ x) (InfixCon lhs rhs))) = + PApp_ (occNameStr x) [lhs, rhs] + view _ = NoPApp_ + +-- A lambda with no guards and no where clauses +pattern SimpleLambda :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsExpr GhcPs +pattern SimpleLambda vs body <- L _ (HsLam _ (MG _ (L _ [L _ (Match _ _ vs (GRHSs _ [L _ (GRHS _ [] body)] (L _ (EmptyLocalBinds _))))]) _)) diff -Nru hlint-2.2.11/src/GHC/Util.hs hlint-3.1.6/src/GHC/Util.hs --- hlint-2.2.11/src/GHC/Util.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/GHC/Util.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,3 +1,5 @@ +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE ViewPatterns #-} module GHC.Util ( module GHC.Util.View @@ -5,55 +7,107 @@ , module GHC.Util.ApiAnnotation , module GHC.Util.HsDecl , module GHC.Util.HsExpr - , module GHC.Util.HsType - , module GHC.Util.LanguageExtensions.Type - , module GHC.Util.Pat - , module GHC.Util.Module - , module GHC.Util.Outputable , module GHC.Util.SrcLoc , module GHC.Util.DynFlags , module GHC.Util.Scope - , module GHC.Util.RdrName , module GHC.Util.Unify - , parsePragmasIntoDynFlags - , parseFileGhcLib, parseExpGhcLib, parseImportGhcLib + , fileToModule + , pattern SrcSpan, srcSpanFilename, srcSpanStartLine', srcSpanStartColumn, srcSpanEndLine', srcSpanEndColumn + , pattern SrcLoc, srcFilename, srcLine, srcColumn + , showSrcSpan, ) where import GHC.Util.View import GHC.Util.FreeVars import GHC.Util.ApiAnnotation import GHC.Util.HsExpr -import GHC.Util.HsType import GHC.Util.HsDecl -import GHC.Util.LanguageExtensions.Type -import GHC.Util.Pat -import GHC.Util.Module -import GHC.Util.Outputable import GHC.Util.SrcLoc import GHC.Util.DynFlags -import GHC.Util.RdrName import GHC.Util.Scope import GHC.Util.Unify -import qualified Language.Haskell.GhclibParserEx.Parse as GhclibParserEx -import Language.Haskell.GhclibParserEx.DynFlags (parsePragmasIntoDynFlags) +import Language.Haskell.GhclibParserEx.GHC.Parser (parseFile) +import Language.Haskell.GhclibParserEx.GHC.Driver.Session (parsePragmasIntoDynFlags) +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable -import HsSyn +import GHC.Hs import Lexer import SrcLoc import DynFlags +import FastString import System.FilePath import Language.Preprocessor.Unlit -parseExpGhcLib :: String -> DynFlags -> ParseResult (LHsExpr GhcPs) -parseExpGhcLib = GhclibParserEx.parseExpression +fileToModule :: FilePath -> String -> DynFlags -> ParseResult (Located (HsModule GhcPs)) +fileToModule filename str flags = + parseFile filename flags + (if takeExtension filename /= ".lhs" then str else unlit filename str) -parseImportGhcLib :: String -> DynFlags -> ParseResult (LImportDecl GhcPs) -parseImportGhcLib = GhclibParserEx.parseImport +{-# COMPLETE SrcSpan #-} +-- | The \"Line'\" thing is because there is already e.g. 'SrcLoc.srcSpanStartLine' +pattern SrcSpan :: String -> Int -> Int -> Int -> Int -> SrcSpan +pattern SrcSpan + { srcSpanFilename + , srcSpanStartLine' + , srcSpanStartColumn + , srcSpanEndLine' + , srcSpanEndColumn + } + <- + (toOldeSpan -> + ( srcSpanFilename + , srcSpanStartLine' + , srcSpanStartColumn + , srcSpanEndLine' + , srcSpanEndColumn + )) + +toOldeSpan :: SrcSpan -> (String, Int, Int, Int, Int) +toOldeSpan (RealSrcSpan span) = + ( unpackFS $ srcSpanFile span + , srcSpanStartLine span + , srcSpanStartCol span + , srcSpanEndLine span + , srcSpanEndCol span + ) +-- TODO: the bad locations are all (-1) right now +-- is this fine? it should be, since noLoc from HSE previously also used (-1) as an invalid location +toOldeSpan (UnhelpfulSpan str) = + ( unpackFS str + , -1 + , -1 + , -1 + , -1 + ) + +{-# COMPLETE SrcLoc #-} +pattern SrcLoc :: String -> Int -> Int -> SrcLoc +pattern SrcLoc + { srcFilename + , srcLine + , srcColumn + } + <- + (toOldeLoc -> + ( srcFilename + , srcLine + , srcColumn + )) + +toOldeLoc :: SrcLoc -> (String, Int, Int) +toOldeLoc (RealSrcLoc loc) = + ( unpackFS $ srcLocFile loc + , srcLocLine loc + , srcLocCol loc + ) +toOldeLoc (UnhelpfulLoc str) = + ( unpackFS str + , -1 + , -1 + ) -parseFileGhcLib :: FilePath -> String -> DynFlags -> ParseResult (Located (HsModule GhcPs)) -parseFileGhcLib filename str flags = - GhclibParserEx.parseFile filename flags - (if takeExtension filename /= ".lhs" then str else unlit filename str) +showSrcSpan :: SrcSpan -> String +showSrcSpan = unsafePrettyPrint diff -Nru hlint-2.2.11/src/Grep.hs hlint-3.1.6/src/Grep.hs --- hlint-2.2.11/src/Grep.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/Grep.hs 2020-06-14 18:45:05.000000000 +0000 @@ -4,35 +4,38 @@ import Hint.All import Apply import Config.Type -import HSE.All +import GHC.All import Control.Monad import Data.List import Util import Idea -import qualified HsSyn as GHC +import qualified GHC.Hs as GHC import qualified BasicTypes as GHC +import qualified Outputable +import qualified ErrUtils +import Lexer import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances import SrcLoc as GHC hiding (mkSrcSpan) +import GHC.Util.DynFlags +import Bag runGrep :: String -> ParseFlags -> [FilePath] -> IO () runGrep patt flags files = do - exp <- case parseExp patt of - ParseOk x -> return x - ParseFailed sl msg -> - exitMessage $ (if "Parse error" `isPrefixOf` msg then msg else "Parse error in pattern: " ++ msg) ++ "\n" ++ - patt ++ "\n" ++ - replicate (srcColumn sl - 1) ' ' ++ "^" - let scope = scopeCreate $ Module an Nothing [] [] [] - let unit = GHC.noLoc $ GHC.ExplicitTuple GHC.noExt [] GHC.Boxed - let rule = hintRules [HintRule Suggestion "grep" scope exp (Tuple an Boxed []) Nothing [] - -- Todo : Replace these with "proper" GHC expressions. - (extendInstances mempty) (extendInstances unit) (extendInstances unit) Nothing] + exp <- case parseExpGhcWithMode flags patt of + POk _ a -> pure a + PFailed ps -> exitMessage $ + let (_, errs) = getMessages ps baseDynFlags + errMsg = head (bagToList errs) + msg = Outputable.showSDoc baseDynFlags $ ErrUtils.pprLocErrMsg errMsg + in "Failed to parse " ++ msg ++ ", when parsing:\n " ++ patt + let ghcUnit = GHC.noLoc $ GHC.ExplicitTuple GHC.noExtField [] GHC.Boxed + let rule = hintRules [HintRule Suggestion "grep" [] mempty (extendInstances exp) (extendInstances ghcUnit) Nothing] forM_ files $ \file -> do res <- parseModuleEx flags file Nothing case res of Left (ParseError sl msg ctxt) -> - print $ rawIdeaN Error (if "Parse error" `isPrefixOf` msg then msg else "Parse error: " ++ msg) (mkSrcSpan sl sl) ctxt Nothing [] + print $ rawIdeaN Error (if "Parse error" `isPrefixOf` msg then msg else "Parse error: " ++ msg) sl ctxt Nothing [] Right m -> forM_ (applyHints [] rule [m]) $ \i -> print i{ideaHint="", ideaTo=Nothing} diff -Nru hlint-2.2.11/src/Hint/All.hs hlint-3.1.6/src/Hint/All.hs --- hlint-2.2.11/src/Hint/All.hs 2020-01-09 16:49:48.000000000 +0000 +++ hlint-3.1.6/src/Hint/All.hs 2020-05-21 15:48:31.000000000 +0000 @@ -1,6 +1,6 @@ module Hint.All( - Hint(..), DeclHint, ModuHint, + Hint(..), ModuHint, resolveHints, hintRules, builtinHints ) where @@ -44,41 +44,38 @@ builtin :: HintBuiltin -> Hint builtin x = case x of - -- Hse. - HintLambda -> decl lambdaHint - -- Ghc. + HintLambda -> decl lambdaHint HintImport -> modu importHint HintExport -> modu exportHint HintComment -> modu commentHint HintPragma -> modu pragmaHint HintDuplicate -> mods duplicateHint HintRestrict -> mempty{hintModule=restrictHint} - HintList -> decl' listHint - HintNewType -> decl' newtypeHint - HintUnsafe -> decl' unsafeHint - HintListRec -> decl' listRecHint - HintNaming -> decl' namingHint - HintBracket -> decl' bracketHint - HintSmell -> mempty{hintDecl'=smellHint,hintModule=smellModuleHint} - HintPattern -> decl' patternHint - HintMonad -> decl' monadHint + HintList -> decl listHint + HintNewType -> decl newtypeHint + HintUnsafe -> decl unsafeHint + HintListRec -> decl listRecHint + HintNaming -> decl namingHint + HintBracket -> decl bracketHint + HintSmell -> mempty{hintDecl=smellHint,hintModule=smellModuleHint} + HintPattern -> decl patternHint + HintMonad -> decl monadHint HintExtensions -> modu extensionsHint where wrap = timed "Hint" (drop 4 $ show x) . forceList decl f = mempty{hintDecl=const $ \a b c -> wrap $ f a b c} - decl' f = mempty{hintDecl'=const $ \a b c -> wrap $ f a b c} modu f = mempty{hintModule=const $ \a b -> wrap $ f a b} mods f = mempty{hintModules=const $ \a -> wrap $ f a} -- | A list of builtin hints, currently including entries such as @\"List\"@ and @\"Bracket\"@. builtinHints :: [(String, Hint)] -builtinHints = [(drop 4 $ show h, builtin h) | h <- [minBound .. maxBound]] +builtinHints = [(drop 4 $ show h, builtin h) | h <- enumerate] -- | Transform a list of 'HintBuiltin' or 'HintRule' into a 'Hint'. resolveHints :: [Either HintBuiltin HintRule] -> Hint resolveHints xs = - mconcat $ mempty{hintDecl'=const $ readMatch' rights} : map builtin (nubOrd lefts) + mconcat $ mempty{hintDecl=const $ readMatch rights} : map builtin (nubOrd lefts) where (lefts,rights) = partitionEithers xs -- | Transform a list of 'HintRule' into a 'Hint'. diff -Nru hlint-2.2.11/src/Hint/Bracket.hs hlint-3.1.6/src/Hint/Bracket.hs --- hlint-2.2.11/src/Hint/Bracket.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/Hint/Bracket.hs 2020-06-14 18:45:05.000000000 +0000 @@ -35,6 +35,13 @@ yes = (($1)) -- @Warning ($1) no = (+5) yes = ((+5)) -- @Warning (+5) +issue909 = case 0 of { _ | n <- (0 :: Int) -> n } +issue909 = foo (\((x :: z) -> y) -> 9 + x * 7) +issue909 = foo (\((x : z) -> y) -> 9 + x * 7) -- \(x : z -> y) -> 9 + x * 7 +issue909 = let ((x:: y) -> z) = q in q +issue909 = do {((x :: y) -> z) <- e; return 1} +issue970 = (f x +) (g x) -- f x + (g x) @NoRefactor +issue969 = (Just \x -> x || x) *> Just True -- type bracket reduction foo :: (Int -> Int) -> Int @@ -56,6 +63,7 @@ yes = operator foo $ operator -- operator foo operator no = operator foo $ operator bar yes = return $ Record{a=b} +no = f $ [1,2..5] -- f [1,2..5] @NoRefactor: apply-refact bug; see apply-refact #51 -- $/bracket rotation tests yes = (b $ c d) ++ e -- b (c d) ++ e @@ -83,34 +91,37 @@ special = foo $ f{x=1} special = foo $ Rec{x=1} special = foo (f{x=1}) +loadCradleOnlyonce = skipManyTill anyMessage (message @PublishDiagnosticsNotification) -} module Hint.Bracket(bracketHint) where -import Hint.Type(DeclHint',Idea(..),rawIdea',warn',suggest',Severity(..),toSS') +import Hint.Type(DeclHint,Idea(..),rawIdea,warn,suggest,Severity(..),toRefactSrcSpan,toSS) import Data.Data -import Data.Generics.Uniplate.Operations +import Data.List.Extra +import Data.Generics.Uniplate.DataOnly import Refact.Types -import HsSyn +import GHC.Hs import Outputable import SrcLoc import GHC.Util import Language.Haskell.GhclibParserEx.GHC.Hs.Expr +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable -bracketHint :: DeclHint' +bracketHint :: DeclHint bracketHint _ _ x = concatMap (\x -> bracket prettyExpr isPartialAtom True x ++ dollar x) (childrenBi (descendBi annotations x) :: [LHsExpr GhcPs]) ++ concatMap (bracket unsafePrettyPrint (const False) False) (childrenBi x :: [LHsType GhcPs]) ++ - concatMap (bracket unsafePrettyPrint (const False) False) (childrenBi x :: [Pat GhcPs]) ++ + concatMap (bracket unsafePrettyPrint (const False) False) (childrenBi x :: [LPat GhcPs]) ++ concatMap fieldDecl (childrenBi x) where -- Brackets the roots of annotations are fine, so we strip them. annotations :: AnnDecl GhcPs -> AnnDecl GhcPs annotations= descendBi $ \x -> case (x :: LHsExpr GhcPs) of - LL l (HsPar _ x) -> x + L l (HsPar _ x) -> x x -> x -- If we find ourselves in the context of a section and we want to @@ -120,8 +131,8 @@ -- latter (in contrast to the HSE pretty printer). This patches things -- up. prettyExpr :: LHsExpr GhcPs -> String -prettyExpr s@(LL _ SectionL{}) = unsafePrettyPrint (noLoc (HsPar noExt s) :: LHsExpr GhcPs) -prettyExpr s@(LL _ SectionR{}) = unsafePrettyPrint (noLoc (HsPar noExt s) :: LHsExpr GhcPs) +prettyExpr s@(L _ SectionL{}) = unsafePrettyPrint (noLoc (HsPar noExtField s) :: LHsExpr GhcPs) +prettyExpr s@(L _ SectionR{}) = unsafePrettyPrint (noLoc (HsPar noExtField s) :: LHsExpr GhcPs) prettyExpr x = unsafePrettyPrint x -- Dirty, should add to Brackets type class I think @@ -137,79 +148,78 @@ -- 'Just _' if at least one set of parens were removed. 'Nothing' if -- zero parens were removed. -remParens' :: Brackets' a => a -> Maybe a -remParens' = fmap go . remParen' +remParens' :: Brackets a => a -> Maybe a +remParens' = fmap go . remParen where - go e = maybe e go (remParen' e) + go e = maybe e go (remParen e) isPartialAtom :: LHsExpr GhcPs -> Bool -- Might be '$x', which was really '$ x', but TH enabled misparsed it. -isPartialAtom (LL _ (HsSpliceE _ (HsTypedSplice _ HasDollar _ _) )) = True -isPartialAtom (LL _ (HsSpliceE _ (HsUntypedSplice _ HasDollar _ _) )) = True +isPartialAtom (L _ (HsSpliceE _ (HsTypedSplice _ HasDollar _ _) )) = True +isPartialAtom (L _ (HsSpliceE _ (HsUntypedSplice _ HasDollar _ _) )) = True isPartialAtom x = isRecConstr x || isRecUpdate x -bracket :: forall a . (Data a, Data (SrcSpanLess a), HasSrcSpan a, Outputable a, Brackets' a) => (a -> String) -> (a -> Bool) -> Bool -> a -> [Idea] +bracket :: forall a . (Data a, Data (SrcSpanLess a), HasSrcSpan a, Outputable a, Brackets a) => (a -> String) -> (a -> Bool) -> Bool -> a -> [Idea] bracket pretty isPartialAtom root = f Nothing where msg = "Redundant bracket" - -- 'f' is a (generic) function over types in 'Brackets' + -- 'f' is a (generic) function over types in 'Brackets -- (expressions, patterns and types). Arguments are, 'f (Maybe -- (index, parent, gen)) child'. - f :: (HasSrcSpan a, Data a, Outputable a, Brackets' a) => Maybe (Int, a , a -> a) -> a -> [Idea] + f :: (HasSrcSpan a, Data a, Outputable a, Brackets a) => Maybe (Int, a , a -> a) -> a -> [Idea] -- No context. Removing parentheses from 'x' succeeds? f Nothing o@(remParens' -> Just x) -- If at the root, or 'x' is an atom, 'x' parens are redundant. - | root || isAtom' x + | root || isAtom x , not $ isPartialAtom x = - (if isAtom' x then bracketError else bracketWarning) msg o x : g x + (if isAtom x then bracketError else bracketWarning) msg o x : g x -- In some context, removing parentheses from 'x' succeeds and 'x' -- is atomic? f Just{} o@(remParens' -> Just x) - | isAtom' x + | isAtom x , not $ isPartialAtom x = bracketError msg o x : g x -- In some context, removing parentheses from 'x' succeeds. Does -- 'x' actually need bracketing in this context? f (Just (i, o, gen)) v@(remParens' -> Just x) - | not $ needBracket' i o x, not $ isPartialAtom x = - rawIdea' Suggestion msg (getLoc o) (pretty o) (Just (pretty (gen x))) [] [r] : g x + | not $ needBracket i o x, not $ isPartialAtom x = + rawIdea Suggestion msg (getLoc v) (pretty o) (Just (pretty (gen x))) [] [r] : g x where typ = findType (unLoc v) - r = Replace typ (toSS' v) [("x", toSS' x)] "x" + r = Replace typ (toSS v) [("x", toSS x)] "x" -- Regardless of the context, there are no parentheses to remove -- from 'x'. f _ x = g x - g :: (HasSrcSpan a, Data a, Outputable a, Brackets' a) => a -> [Idea] + g :: (HasSrcSpan a, Data a, Outputable a, Brackets a) => a -> [Idea] -- Enumerate over all the immediate children of 'o' looking for -- redundant parentheses in each. - g o = concat [f (Just (i, o, gen)) x | (i, (x, gen)) <- zip [0..] $ holes o] + g o = concat [f (Just (i, o, gen)) x | (i, (x, gen)) <- zipFrom 0 $ holes o] bracketWarning :: (HasSrcSpan a, HasSrcSpan b, Data (SrcSpanLess b), Outputable a, Outputable b) => String -> a -> b -> Idea bracketWarning msg o x = - suggest' msg o x [Replace (findType (unLoc x)) (toSS' o) [("x", toSS' x)] "x"] + suggest msg o x [Replace (findType (unLoc x)) (toSS o) [("x", toSS x)] "x"] bracketError :: (HasSrcSpan a, HasSrcSpan b, Data (SrcSpanLess b), Outputable a, Outputable b ) => String -> a -> b -> Idea bracketError msg o x = - warn' msg o x [Replace (findType (unLoc x)) (toSS' o) [("x", toSS' x)] "x"] + warn msg o x [Replace (findType (unLoc x)) (toSS o) [("x", toSS x)] "x"] fieldDecl :: LConDeclField GhcPs -> [Idea] -fieldDecl o@(LL loc f@ConDeclField{cd_fld_type=v@(LL l (HsParTy _ c))}) = - let r = LL loc (f{cd_fld_type=c}) :: LConDeclField GhcPs in - [rawIdea' Suggestion "Redundant bracket" loc +fieldDecl o@(L loc f@ConDeclField{cd_fld_type=v@(L l (HsParTy _ c))}) = + let r = L loc (f{cd_fld_type=c}) :: LConDeclField GhcPs in + [rawIdea Suggestion "Redundant bracket" l (showSDocUnsafe $ ppr_fld o) -- Note this custom printer! (Just (showSDocUnsafe $ ppr_fld r)) [] - [Replace Type (toSS' v) [("x", toSS' c)] "x"]] + [Replace Type (toSS v) [("x", toSS c)] "x"]] where -- If we call 'unsafePrettyPrint' on a field decl, we won't like -- the output (e.g. "[foo, bar] :: T"). Here we use a custom -- printer to work around (snarfed from -- https://hackage.haskell.org/package/ghc-lib-parser-8.8.1/docs/src/HsTypes.html#pprConDeclFields). - ppr_fld (LL _ ConDeclField { cd_fld_names = ns, cd_fld_type = ty, cd_fld_doc = doc }) + ppr_fld (L _ ConDeclField { cd_fld_names = ns, cd_fld_type = ty, cd_fld_doc = doc }) = ppr_names ns <+> dcolon <+> ppr ty <+> ppr_mbDoc doc - ppr_fld (LL _ (XConDeclField x)) = ppr x - ppr_fld _ = undefined -- '{-# COMPLETE LL #-}' + ppr_fld (L _ (XConDeclField x)) = ppr x ppr_names [n] = ppr n ppr_names ns = sep (punctuate comma (map ppr ns)) @@ -220,26 +230,32 @@ dollar :: LHsExpr GhcPs -> [Idea] dollar = concatMap f . universe where - f x = [ suggest' "Redundant $" x y [r]| o@(LL loc (OpApp _ a d b)) <- [x], isDol d - , let y = noLoc (HsApp noExt a b) :: LHsExpr GhcPs - , not $ needBracket' 0 y a - , not $ needBracket' 1 y b + f x = [ (suggest "Redundant $" x y [r]){ideaSpan = getLoc d} | o@(L _ (OpApp _ a d b)) <- [x], isDol d + , let y = noLoc (HsApp noExtField a b) :: LHsExpr GhcPs + , not $ needBracket 0 y a + , not $ needBracket 1 y b , not $ isPartialAtom b - , let r = Replace Expr (toSS' x) [("a", toSS' a), ("b", toSS' b)] "a b"] + , let r = Replace Expr (toSS x) [("a", toSS a), ("b", toSS b)] "a b"] ++ - [ suggest' "Move brackets to avoid $" x (t y) [r] - |(t, e@(LL _ (HsPar _ (LL _ (OpApp _ a1 op1 a2))))) <- splitInfix x + [ suggest "Move brackets to avoid $" x (t y) [r] + |(t, e@(L _ (HsPar _ (L _ (OpApp _ a1 op1 a2))))) <- splitInfix x , isDol op1 - , isVar a1 || isApp a1 || isPar a1, not $ isAtom' a2 + , isVar a1 || isApp a1 || isPar a1, not $ isAtom a2 , varToStr a1 /= "select" -- special case for esqueleto, see #224 - , let y = noLoc $ HsApp noExt a1 (noLoc (HsPar noExt a2)) - , let r = Replace Expr (toSS' e) [("a", toSS' a1), ("b", toSS' a2)] "a (b)" ] + , let y = noLoc $ HsApp noExtField a1 (noLoc (HsPar noExtField a2)) + , let r = Replace Expr (toSS e) [("a", toSS a1), ("b", toSS a2)] "a (b)" ] ++ -- Special case of (v1 . v2) <$> v3 - [ suggest' "Redundant bracket" x y [] - | LL _ (OpApp _ (LL _ (HsPar _ o1@(LL _ (OpApp _ v1 (isDot -> True) v2)))) o2 v3) <- [x], varToStr o2 == "<$>" - , let y = noLoc (OpApp noExt o1 o2 v3) :: LHsExpr GhcPs] + [ (suggest "Redundant bracket" x y [r]){ideaSpan = locPar} + | L _ (OpApp _ (L locPar (HsPar _ o1@(L locNoPar (OpApp _ v1 (isDot -> True) v2)))) o2 v3) <- [x], varToStr o2 == "<$>" + , let y = noLoc (OpApp noExtField o1 o2 v3) :: LHsExpr GhcPs + , let r = Replace Expr (toRefactSrcSpan locPar) [("a", toRefactSrcSpan locNoPar)] "a"] + ++ + [ suggest "Redundant section" x y [] + | L _ (HsApp _ (L _ (HsPar _ (L _ (SectionL _ a b)))) c) <- [x] + -- , error $ show (unsafePrettyPrint a, gshow b, unsafePrettyPrint c) + , let y = noLoc $ OpApp noExtField a b c :: LHsExpr GhcPs] splitInfix :: LHsExpr GhcPs -> [(LHsExpr GhcPs -> LHsExpr GhcPs, LHsExpr GhcPs)] -splitInfix (LL l (OpApp _ lhs op rhs)) = - [(LL l . OpApp noExt lhs op, rhs), (\lhs -> LL l (OpApp noExt lhs op rhs), lhs)] +splitInfix (L l (OpApp _ lhs op rhs)) = + [(L l . OpApp noExtField lhs op, rhs), (\lhs -> L l (OpApp noExtField lhs op rhs), lhs)] splitInfix _ = [] diff -Nru hlint-2.2.11/src/Hint/Comment.hs hlint-3.1.6/src/Hint/Comment.hs --- hlint-2.2.11/src/Hint/Comment.hs 2019-09-24 17:55:46.000000000 +0000 +++ hlint-3.1.6/src/Hint/Comment.hs 2020-05-13 11:33:34.000000000 +0000 @@ -38,12 +38,12 @@ where isMultiline = isCommentMultiline comm s = commentText comm - name = takeWhile (\x -> isAlphaNum x || x == '_') $ dropWhile isSpace s + name = takeWhile (\x -> isAlphaNum x || x == '_') $ trimStart s chk _ = [] grab :: String -> Located AnnotationComment -> String -> Idea grab msg o@(L pos _) s2 = let s1 = commentText o in - rawIdea' Suggestion msg pos (f s1) (Just $ f s2) [] refact + rawIdea Suggestion msg pos (f s1) (Just $ f s2) [] refact where f s = if isCommentMultiline o then "{-" ++ s ++ "-}" else "--" ++ s - refact = [ModifyComment (toRefactSrcSpan (ghcSpanToHSE pos)) (f s2)] + refact = [ModifyComment (toRefactSrcSpan pos) (f s2)] diff -Nru hlint-2.2.11/src/Hint/Duplicate.hs hlint-3.1.6/src/Hint/Duplicate.hs --- hlint-2.2.11/src/Hint/Duplicate.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/Hint/Duplicate.hs 2020-06-24 11:09:26.000000000 +0000 @@ -6,44 +6,48 @@ If you have n the same, error out +foo = a where {a = 1; b = 2; c = 3} \ +bar = a where {a = 1; b = 2; c = 3} -- ??? @NoRefactor main = do a; a; a; a -main = do a; a; a; a; a; a -- ??? -main = do a; a; a; a; a; a; a -- ??? -main = do (do b; a; a; a); do (do c; a; a; a) -- ??? -main = do a; a; a; b; a; a; a -- ??? +main = do a; a; a; a; a; a -- ??? @NoRefactor: refactoring not supported for duplication hints. +main = do a; a; a; a; a; a; a -- ??? @NoRefactor +main = do (do b; a; a; a); do (do c; a; a; a) -- ??? @NoRefactor +main = do a; a; a; b; a; a; a -- ??? @NoRefactor main = do a; a; a; b; a; a -foo = a where {a = 1; b = 2; c = 3}; bar = a where {a = 1; b = 2; c = 3} -- ??? -{-# ANN main "HLint: ignore Reduce duplication" #-}; main = do a; a; a; a; a; a -- @Ignore ??? -{-# HLINT ignore main "Reduce duplication" #-}; main = do a; a; a; a; a; a -- @Ignore ??? -{- HLINT ignore main "Reduce duplication" -}; main = do a; a; a; a; a; a -- @Ignore ??? +{-# ANN main "HLint: ignore Reduce duplication" #-}; main = do a; a; a; a; a; a -- @Ignore ??? @NoRefactor +{-# HLINT ignore main "Reduce duplication" #-}; main = do a; a; a; a; a; a -- @Ignore ??? @NoRefactor +{- HLINT ignore main "Reduce duplication" -}; main = do a; a; a; a; a; a -- @Ignore ??? @NoRefactor -} module Hint.Duplicate(duplicateHint) where -import Hint.Type (CrossHint, ModuleEx(..), Idea(..),rawIdeaN',Severity(Suggestion,Warning),showSrcLoc,ghcSrcLocToHSE) +import Hint.Type (CrossHint, ModuleEx(..), Idea(..),rawIdeaN,Severity(Suggestion,Warning)) import Data.Data -import Data.Generics.Uniplate.Operations +import Data.Generics.Uniplate.DataOnly import Data.Default import Data.Maybe import Data.Tuple.Extra import Data.List hiding (find) +import qualified Data.List.NonEmpty as NE import qualified Data.Map as Map import SrcLoc -import HsSyn +import GHC.Hs import Outputable import Bag import GHC.Util +import Language.Haskell.GhclibParserEx.GHC.Hs import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable duplicateHint :: CrossHint duplicateHint ms = -- Do expressions. dupes [ (m, d, y) | (m, d, x) <- ds - , HsDo _ _ (LL _ y) :: HsExpr GhcPs <- universeBi x + , HsDo _ _ (L _ y) :: HsExpr GhcPs <- universeBi x ] ++ -- Bindings in a 'let' expression or a 'where' clause. dupes [ (m, d, y) @@ -53,22 +57,22 @@ ] where ds = [(modName m, fromMaybe "" (declName d), unLoc d) - | ModuleEx _ _ m _ <- map snd ms + | ModuleEx m _ <- map snd ms , d <- hsmodDecls (unLoc m)] dupes :: (Outputable e, Data e) => [(String, String, [Located e])] -> [Idea] dupes ys = - [(rawIdeaN' + [(rawIdeaN (if length xs >= 5 then Hint.Type.Warning else Suggestion) "Reduce duplication" p1 (unlines $ map unsafePrettyPrint xs) - (Just $ "Combine with " ++ - showSrcLoc (ghcSrcLocToHSE (srcSpanStart p2))) [] + (Just $ "Combine with " ++ showSrcSpan p2) + [] ){ideaModule = [m1, m2], ideaDecl = [d1, d2]} | ((m1, d1, SrcSpanD p1), (m2, d2, SrcSpanD p2), xs) <- duplicateOrdered 3 $ map f ys] where f (m, d, xs) = - [((m, d, SrcSpanD (getLoc x)), extendInstances (stripLocs' x)) | x <- xs] + [((m, d, SrcSpanD (getLoc x)), extendInstances (stripLocs x)) | x <- xs] --------------------------------------------------------------------- -- DUPLICATE FINDING @@ -93,20 +97,24 @@ duplicateOrdered threshold xs = concat $ concat $ snd $ mapAccumL f (Dupe def Map.empty) xs where f :: Dupe pos val -> [(pos, val)] -> (Dupe pos val, [[(pos, pos, [val])]]) - f d xs = second overlaps $ mapAccumL (g pos) d $ takeWhile ((>= threshold) . length) $ tails xs + f d xs = second overlaps $ mapAccumL (g pos) d $ onlyAtLeast threshold $ tails xs where pos = Map.fromList $ zip (map fst xs) [0..] - g :: Map.Map pos Int -> Dupe pos val -> [(pos, val)] -> (Dupe pos val, [(pos, pos, [val])]) + g :: Map.Map pos Int -> Dupe pos val -> NE.NonEmpty (pos, val) -> (Dupe pos val, [(pos, pos, [val])]) g pos d xs = (d2, res) where res = [(p,pme,take mx vs) | i >= threshold ,let mx = maybe i (\x -> min i $ (pos Map.! pme) - x) $ Map.lookup p pos ,mx >= threshold] - vs = map snd xs + vs = NE.toList $ snd <$> xs (p,i) = find vs d - pme = fst $ head xs + pme = fst $ NE.head xs d2 = add pme vs d + onlyAtLeast n = mapMaybe $ \l -> case l of + x:xs | length l >= n -> Just (x NE.:| xs) + _ -> Nothing + overlaps (x@((_,_,n):_):xs) = x : overlaps (drop (length n - 1) xs) overlaps (x:xs) = x : overlaps xs overlaps [] = [] diff -Nru hlint-2.2.11/src/Hint/Export.hs hlint-3.1.6/src/Hint/Export.hs --- hlint-2.2.11/src/Hint/Export.hs 2019-11-02 16:50:13.000000000 +0000 +++ hlint-3.1.6/src/Hint/Export.hs 2020-05-13 11:33:34.000000000 +0000 @@ -3,45 +3,45 @@ main = 1 -module Foo where foo = 1 -- module Foo(module Foo) where +module Foo where foo = 1 -- module Foo(module Foo) where @NoRefactor module Foo(foo) where foo = 1 -module Foo(module Foo) where foo = 1 -- @Ignore module Foo(...) where -module Foo(module Foo, foo) where foo = 1 -- module Foo(..., foo) where +module Foo(module Foo) where foo = 1 -- @Ignore module Foo(...) where @NoRefactor +module Foo(module Foo, foo) where foo = 1 -- module Foo(..., foo) where @NoRefactor -} {-# LANGUAGE TypeFamilies #-} module Hint.Export(exportHint) where -import Hint.Type(ModuHint, ModuleEx(..),ideaNote,ignore',Note(..)) +import Hint.Type(ModuHint, ModuleEx(..),ideaNote,ignore,Note(..)) -import HsSyn +import GHC.Hs import Module import SrcLoc import OccName import RdrName exportHint :: ModuHint -exportHint _ (ModuleEx _ _ (LL s m@HsModule {hsmodName = Just name, hsmodExports = exports}) _) +exportHint _ (ModuleEx (L s m@HsModule {hsmodName = Just name, hsmodExports = exports}) _) | Nothing <- exports = - let r = o{ hsmodExports = Just (noLoc [noLoc (IEModuleContents noExt name)] )} in - [(ignore' "Use module export list" (L s o) (noLoc r) []){ideaNote = [Note "an explicit list is usually better"]}] + let r = o{ hsmodExports = Just (noLoc [noLoc (IEModuleContents noExtField name)] )} in + [(ignore "Use module export list" (L s o) (noLoc r) []){ideaNote = [Note "an explicit list is usually better"]}] | Just (L _ xs) <- exports , mods <- [x | x <- xs, isMod x] , modName <- moduleNameString (unLoc name) - , names <- [ moduleNameString (unLoc n) | (LL _ (IEModuleContents _ n)) <- mods] + , names <- [ moduleNameString (unLoc n) | (L _ (IEModuleContents _ n)) <- mods] , exports' <- [x | x <- xs, not (matchesModName modName x)] , modName `elem` names = let dots = mkRdrUnqual (mkVarOcc " ... ") - r = o{ hsmodExports = Just (noLoc (noLoc (IEVar noExt (noLoc (IEName (noLoc dots)))) : exports') )} + r = o{ hsmodExports = Just (noLoc (noLoc (IEVar noExtField (noLoc (IEName (noLoc dots)))) : exports') )} in - [ignore' "Use explicit module export list" (L s o) (noLoc r) []] + [ignore "Use explicit module export list" (L s o) (noLoc r) []] where o = m{hsmodImports=[], hsmodDecls=[], hsmodDeprecMessage=Nothing, hsmodHaddockModHeader=Nothing } - isMod (LL _ (IEModuleContents _ _)) = True + isMod (L _ (IEModuleContents _ _)) = True isMod _ = False - matchesModName m (LL _ (IEModuleContents _ (L _ n))) = moduleNameString n == m + matchesModName m (L _ (IEModuleContents _ (L _ n))) = moduleNameString n == m matchesModName _ _ = False exportHint _ _ = [] diff -Nru hlint-2.2.11/src/Hint/Extensions.hs hlint-3.1.6/src/Hint/Extensions.hs --- hlint-2.2.11/src/Hint/Extensions.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/Hint/Extensions.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,4 +1,4 @@ -{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE LambdaCase, NamedFieldPuns #-} {- Suggest removal of unnecessary extensions @@ -33,6 +33,33 @@ foo x = let !y = x in y {-# LANGUAGE BangPatterns #-} \ data Foo = Foo !Int -- +{-# LANGUAGE TypeOperators #-} \ +data (<+>) a b = Foo a b +{-# LANGUAGE TypeOperators #-} \ +data Foo a b = a :+ b -- +{-# LANGUAGE TypeOperators #-} \ +type (<+>) a b = Foo a b +{-# LANGUAGE TypeOperators #-} \ +type Foo a b = a :+ b +{-# LANGUAGE TypeOperators, TypeFamilies #-} \ +type family Foo a b :: Type where Foo a b = a :+ b +{-# LANGUAGE TypeOperators, TypeFamilies #-} \ +type family Foo a b :: Type where Foo a b = (<+>) a b -- {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators, TypeFamilies #-} \ +class Foo a where data (<+>) a +{-# LANGUAGE TypeOperators, TypeFamilies #-} \ +class Foo a where foo :: a -> Int <+> Bool +{-# LANGUAGE TypeOperators #-} \ +class (<+>) a where +{-# LANGUAGE TypeOperators #-} \ +foo :: Int -> Double <+> Bool \ +foo x = y +{-# LANGUAGE TypeOperators #-} \ +foo :: Int -> (<+>) Double Bool \ +foo x = y -- +{-# LANGUAGE TypeOperators #-} \ +(<+>) :: Int -> Int -> Int \ +x <+> y = x + y -- {-# LANGUAGE RecordWildCards #-} \ record field = Record{..} {-# LANGUAGE RecordWildCards #-} \ @@ -78,6 +105,13 @@ f x = case x of (# a, b #) -> a {-# LANGUAGE GeneralizedNewtypeDeriving,UnboxedTuples #-} \ newtype T m a = T (m a) deriving (PrimMonad) +{-# LANGUAGE InstanceSigs #-} \ +instance Eq a => Eq (T a) where \ + (==) :: T a -> T a -> Bool \ + (==) (T x) (T y) = x==y +{-# LANGUAGE InstanceSigs #-} \ +instance Eq a => Eq (T a) where \ + (==) (T x) (T y) = x==y -- {-# LANGUAGE DefaultSignatures #-} \ class Val a where; val :: a -- {-# LANGUAGE DefaultSignatures #-} \ @@ -104,6 +138,16 @@ main = "test" {-# LANGUAGE OverloadedStrings #-} \ main = id -- +{-# LANGUAGE OverloadedLists #-} \ +main = [1] +{-# LANGUAGE OverloadedLists #-} \ +main [1] = True +{-# LANGUAGE OverloadedLists #-} \ +main = id -- +{-# LANGUAGE OverloadedLabels #-} \ +main = #foo +{-# LANGUAGE OverloadedLabels #-} \ +main = id -- {-# LANGUAGE DeriveAnyClass #-} \ main = id -- {-# LANGUAGE DeriveAnyClass #-} \ @@ -135,53 +179,100 @@ data Set (cxt :: * -> *) a = Set [a] -- @Note Extension KindSignatures is implied by PolyKinds {-# LANGUAGE QuasiQuotes, OverloadedStrings #-} \ main = putStrLn [f|{T.intercalate "blah" []}|] +{-# LANGUAGE NamedFieldPuns #-} \ +foo = x{bar} +{-# LANGUAGE PatternSynonyms #-} \ +module Foo (pattern Bar) where x = 42 +{-# LANGUAGE PatternSynonyms #-} \ +import Foo (pattern Bar); x = 42 +{-# LANGUAGE PatternSynonyms #-} \ +pattern Foo s <- Bar s _ where Foo s = Bar s s +{-# LANGUAGE PatternSynonyms #-} \ +x = 42 -- +{-# LANGUAGE MultiWayIf #-} \ +x = if | b1 -> v1 | b2 -> v2 | otherwise -> v3 +{-# LANGUAGE MultiWayIf #-} \ +x = if b1 then v1 else if b2 then v2 else v3 -- +static = 42 +{-# LANGUAGE NamedFieldPuns #-} \ +foo Foo{x} = x +{-# LANGUAGE NamedFieldPuns #-} \ +foo = Foo{x} +{-# LANGUAGE NamedFieldPuns #-} \ +foo = bar{x} +{-# LANGUAGE NamedFieldPuns #-} -- +{-# LANGUAGE NumericUnderscores #-} \ +lessThanPi = (< 3.141_592_653_589_793) +{-# LANGUAGE NumericUnderscores #-} \ +oneMillion = 0xf4__240 +{-# LANGUAGE NumericUnderscores #-} \ +avogadro = 6.022140857e+23 -- +{-# LANGUAGE StaticPointers #-} \ +static = 42 -- +{-# LANGUAGE Trustworthy #-} +{-# LANGUAGE Trustworthy, NamedFieldPuns #-} -- {-# LANGUAGE Trustworthy #-} +{-# LANGUAGE Haskell2010 #-} +{-# LANGUAGE NoStarIsType, ExplicitNamespaces #-} \ +import GHC.TypeLits(KnownNat, type (+), type (*)) +{-# LANGUAGE LambdaCase, MultiWayIf, NoRebindableSyntax #-} \ +foo = \case True -> 3 -- {-# LANGUAGE LambdaCase, NoRebindableSyntax #-} -} module Hint.Extensions(extensionsHint) where -import Hint.Type(ModuHint, rawIdea',Severity(Warning),Note(..),toSS',ghcAnnotations,ghcModule,extensionImpliedBy,extensionImplies) -import Language.Haskell.Exts.Extension +import Hint.Type(ModuHint, rawIdea,Severity(Warning),Note(..),toSS,ghcAnnotations,ghcModule) +import Extension -import Data.Generics.Uniplate.Operations +import Data.Generics.Uniplate.DataOnly import Control.Monad.Extra +import Data.Maybe import Data.List.Extra -import Data.Ratio import Data.Data import Refact.Types import qualified Data.Set as Set import qualified Data.Map as Map import SrcLoc -import HsSyn +import GHC.Hs import BasicTypes import Class import RdrName -import OccName import ForeignCall + import GHC.Util +import GHC.LanguageExtensions.Type + +import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr +import Language.Haskell.GhclibParserEx.GHC.Hs.Types +import Language.Haskell.GhclibParserEx.GHC.Hs.Decls +import Language.Haskell.GhclibParserEx.GHC.Hs.Binds +import Language.Haskell.GhclibParserEx.GHC.Hs.ImpExp +import Language.Haskell.GhclibParserEx.GHC.Driver.Session +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader extensionsHint :: ModuHint extensionsHint _ x = - [ rawIdea' Hint.Type.Warning "Unused LANGUAGE pragma" + [ rawIdea Hint.Type.Warning "Unused LANGUAGE pragma" sl - (comment (mkLangExts sl exts)) + (comment (mkLanguagePragmas sl exts)) (Just newPragma) - ( [RequiresExtension $ prettyExtension gone | x <- before \\ after, gone <- Map.findWithDefault [] x disappear] ++ - [ Note $ "Extension " ++ prettyExtension x ++ " is " ++ reason x - | x <- explainedRemovals]) - [ModifyComment (toSS' (mkLangExts sl exts)) newPragma] - | (LL sl _, exts) <- langExts $ pragmas (ghcAnnotations x) - , let before = map parseExtension exts - , let after = filter (`Set.member` keep) before + ( [RequiresExtension (show gone) | (_, Just x) <- before \\ after, gone <- Map.findWithDefault [] x disappear] ++ + [ Note $ "Extension " ++ show x ++ " is " ++ reason x + | (_, Just x) <- explainedRemovals]) + [ModifyComment (toSS (mkLanguagePragmas sl exts)) newPragma] + | (L sl _, exts) <- languagePragmas $ pragmas (ghcAnnotations x) + , let before = [(x, readExtension x) | x <- exts] + , let after = filter (maybe True (`Set.member` keep) . snd) before , before /= after , let explainedRemovals - | null after && not (any (`Map.member` implied) before) = [] + | null after && not (any (`Map.member` implied) $ mapMaybe snd before) = [] | otherwise = before \\ after , let newPragma = - if null after then "" else comment (mkLangExts sl $ map prettyExtension after) + if null after then "" else comment (mkLanguagePragmas sl $ map fst after) ] where usedTH :: Bool @@ -191,9 +282,9 @@ -- All the extensions defined to be used. extensions :: Set.Set Extension - extensions = Set.fromList [ parseExtension e - | let exts = concatMap snd $ langExts (pragmas (ghcAnnotations x)) - , e <- exts ] + extensions = Set.fromList $ mapMaybe readExtension $ + concatMap snd $ languagePragmas (pragmas (ghcAnnotations x)) + -- Those extensions we detect to be useful. useful :: Set.Set Extension useful = if usedTH then extensions else Set.filter (`usedExt` ghcModule x) extensions @@ -203,25 +294,27 @@ implied = Map.fromList [ (e, a) | e <- Set.toList useful - , a:_ <- [filter (`Set.member` useful) $ extensionImpliedBy e]] + , a:_ <- [filter (`Set.member` useful) $ extensionImpliedEnabledBy e] + ] -- Those we should keep. keep :: Set.Set Extension keep = useful `Set.difference` Map.keysSet implied -- The meaning of (a,b) is a used to imply b, but has gone, so -- suggest enabling b. + disappear :: Map.Map Extension [Extension] disappear = Map.fromListWith (++) $ nubOrdOn snd -- Only keep one instance for each of a. [ (e, [a]) | e <- Set.toList $ extensions `Set.difference` keep - , a <- extensionImplies e + , a <- fst $ extensionImplies e , a `Set.notMember` useful , usedTH || usedExt a (ghcModule x) ] reason :: Extension -> String reason x = case Map.lookup x implied of - Just a -> "implied by " ++ prettyExtension a + Just a -> "implied by " ++ show a Nothing -> "not used" deriveHaskell = ["Eq","Ord","Enum","Ix","Bounded","Read","Show"] @@ -238,13 +331,14 @@ deriveStock = deriveHaskell ++ deriveGenerics ++ deriveCategory usedExt :: Extension -> Located (HsModule GhcPs) -> Bool -usedExt (EnableExtension x) = used x -usedExt (UnknownExtension "NumDecimals") = hasS isWholeFrac -usedExt (UnknownExtension "DeriveLift") = hasDerive ["Lift"] -usedExt (UnknownExtension "DeriveAnyClass") = not . null . derivesAnyclass . derives -usedExt _ = const True +usedExt NumDecimals = hasS isWholeFrac + -- Only whole number fractions are permitted by NumDecimals + -- extension. Anything not-whole raises an error. +usedExt DeriveLift = hasDerive ["Lift"] +usedExt DeriveAnyClass = not . null . derivesAnyclass . derives +usedExt x = used x -used :: KnownExtension -> Located (HsModule GhcPs) -> Bool +used :: Extension -> Located (HsModule GhcPs) -> Bool used RecursiveDo = hasS isMDo ||^ hasS isRecStmt used ParallelListComp = hasS isParComp used FunctionalDependencies = hasT (un :: FunDep (Located RdrName)) @@ -258,78 +352,108 @@ used EmptyCase = hasS f where f :: HsExpr GhcPs -> Bool - f (HsCase _ _ (MG _ (LL _ []) _)) = True - f (HsLamCase _ (MG _ (LL _ []) _)) = True + f (HsCase _ _ (MG _ (L _ []) _)) = True + f (HsLamCase _ (MG _ (L _ []) _)) = True f _ = False used KindSignatures = hasT (un :: HsKind GhcPs) -used BangPatterns = hasS isPBangPat' ||^ hasS isStrictMatch - where - isStrictMatch :: HsMatchContext RdrName -> Bool - isStrictMatch FunRhs{mc_strictness=SrcStrict} = True - isStrictMatch _ = False +used BangPatterns = hasS isPBangPat ||^ hasS isStrictMatch used TemplateHaskell = hasT2' (un :: (HsBracket GhcPs, HsSplice GhcPs)) ||^ hasS f ||^ hasS isSpliceDecl - where - f :: HsBracket GhcPs -> Bool - f VarBr{} = True - f TypBr{} = True - f _ = False + where + f :: HsBracket GhcPs -> Bool + f VarBr{} = True + f TypBr{} = True + f _ = False used ForeignFunctionInterface = hasT (un :: CCallConv) used PatternGuards = hasS f where f :: GRHS GhcPs (LHsExpr GhcPs) -> Bool f (GRHS _ xs _) = g xs - f _ = False -- new ctor + f _ = False -- Extension constructor g :: [GuardLStmt GhcPs] -> Bool g [] = False - g [LL _ BodyStmt{}] = False + g [L _ BodyStmt{}] = False g _ = True -used StandaloneDeriving = hasS isDerivD' -used PatternSignatures = hasS isPatTypeSig' -used RecordWildCards = hasS hasFieldsDotDot ||^ hasS hasPFieldsDotDot' -used RecordPuns = hasS isPFieldPun' ||^ hasS isFieldPun -used NamedFieldPuns = hasS isPFieldPun' ||^ hasS isFieldPun -used UnboxedTuples = has isUnboxedTuple' ||^ has (== Unboxed) ||^ hasS isDeriving - where - -- detect if there are deriving declarations or data ... deriving stuff - -- by looking for the deriving strategy both contain (even if its Nothing) - -- see https://github.com/ndmitchell/hlint/issues/833 for why we care - isDeriving :: Maybe (LDerivStrategy GhcPs) -> Bool - isDeriving _ = True +used StandaloneDeriving = hasS isDerivD +used TypeOperators = hasS tyOpInSig ||^ hasS tyOpInDecl + where + tyOpInSig :: HsType GhcPs -> Bool + tyOpInSig = \case + HsOpTy{} -> True; _ -> False + + tyOpInDecl :: HsDecl GhcPs -> Bool + tyOpInDecl = \case + (TyClD _ (FamDecl _ FamilyDecl{fdLName})) -> isOp fdLName + (TyClD _ SynDecl{tcdLName}) -> isOp tcdLName + (TyClD _ DataDecl{tcdLName}) -> isOp tcdLName + (TyClD _ ClassDecl{tcdLName, tcdATs}) -> any isOp (tcdLName : [fdLName famDecl | L _ famDecl <- tcdATs]) + _ -> False + + isOp (L _ name) = isSymbolRdrName name + +used RecordWildCards = hasS hasFieldsDotDot ||^ hasS hasPFieldsDotDot +used RecordPuns = hasS isPFieldPun ||^ hasS isFieldPun ||^ hasS isFieldPunUpdate +used UnboxedTuples = hasS isUnboxedTuple ||^ hasS (== Unboxed) ||^ hasS isDeriving + where + -- detect if there are deriving declarations or data ... deriving stuff + -- by looking for the deriving strategy both contain (even if its Nothing) + -- see https://github.com/ndmitchell/hlint/issues/833 for why we care + isDeriving :: Maybe (LDerivStrategy GhcPs) -> Bool + isDeriving _ = True used PackageImports = hasS f - where - f :: ImportDecl GhcPs -> Bool - f ImportDecl{ideclPkgQual=Just _} = True - f _ = False -used QuasiQuotes = hasS isQuasiQuote ||^ hasS isTyQuasiQuote' -used ViewPatterns = hasS isPViewPat' -used DefaultSignatures = hasS isClsDefSig' + where + f :: ImportDecl GhcPs -> Bool + f ImportDecl{ideclPkgQual=Just _} = True + f _ = False +used QuasiQuotes = hasS isQuasiQuote ||^ hasS isTyQuasiQuote +used ViewPatterns = hasS isPViewPat +used InstanceSigs = hasS f + where + f :: HsDecl GhcPs -> Bool + f (InstD _ decl) = hasT (un :: Sig GhcPs) decl + f _ = False +used DefaultSignatures = hasS isClsDefSig used DeriveDataTypeable = hasDerive ["Data","Typeable"] used DeriveFunctor = hasDerive ["Functor"] used DeriveFoldable = hasDerive ["Foldable"] used DeriveTraversable = hasDerive ["Traversable","Foldable","Functor"] used DeriveGeneric = hasDerive ["Generic","Generic1"] used GeneralizedNewtypeDeriving = not . null . derivesNewtype' . derives +used MultiWayIf = hasS isMultiIf +used NumericUnderscores = hasS f + where + f :: OverLitVal -> Bool + f (HsIntegral (IL (SourceText t) _ _)) = '_' `elem` t + f (HsFractional (FL (SourceText t) _ _)) = '_' `elem` t + f _ = False + used LambdaCase = hasS isLCase used TupleSections = hasS isTupleSection used OverloadedStrings = hasS isString -used Arrows = hasS f +used OverloadedLists = hasS isListExpr ||^ hasS isListPat where - f :: HsExpr GhcPs -> Bool - f HsProc{} = True - f HsArrApp{} = True - f _ = False -used TransformListComp = hasS f - where - f :: StmtLR GhcPs GhcPs (LHsExpr GhcPs) -> Bool - f TransStmt{} = True - f _ = False + isListExpr :: HsExpr GhcPs -> Bool + isListExpr ExplicitList{} = True + isListExpr ArithSeq{} = True + isListExpr _ = False + + isListPat :: Pat GhcPs -> Bool + isListPat ListPat{} = True + isListPat _ = False + +used OverloadedLabels = hasS isLabel + where + isLabel :: HsExpr GhcPs -> Bool + isLabel HsOverLabel{} = True + isLabel _ = False + +used Arrows = hasS isProc +used TransformListComp = hasS isTransStmt used MagicHash = hasS f ||^ hasS isPrimLiteral - where - f :: RdrName -> Bool - f s = "#" `isSuffixOf` (occNameString . rdrNameOcc) s --- For forwards compatibility, if things ever get added to the --- extension enumeration. -used x = usedExt $ UnknownExtension $ show x + where + f :: RdrName -> Bool + f s = "#" `isSuffixOf` occNameStr s +used PatternSynonyms = hasS isPatSynBind ||^ hasS isPatSynIE +used _= const True hasDerive :: [String] -> Located (HsModule GhcPs) -> Bool hasDerive want = any (`elem` want) . derivesStock' . derives @@ -357,38 +481,35 @@ addDerives nt _ xs = mempty {derivesStock' = stock ,derivesAnyclass = other - ,derivesNewtype' = if maybe True isNewType' nt then filter (`notElem` noDeriveNewtype) xs else []} + ,derivesNewtype' = if maybe True isNewType nt then filter (`notElem` noDeriveNewtype) xs else []} where (stock, other) = partition (`elem` deriveStock) xs derives :: Located (HsModule GhcPs) -> Derives -derives (LL _ m) = mconcat $ map decl (childrenBi m) ++ map idecl (childrenBi m) +derives (L _ m) = mconcat $ map decl (childrenBi m) ++ map idecl (childrenBi m) where idecl :: Located (DataFamInstDecl GhcPs) -> Derives - idecl (LL _ (DataFamInstDecl (HsIB _ FamEqn {feqn_rhs=HsDataDefn {dd_ND=dn, dd_derivs=(LL _ ds)}}))) = g dn ds + idecl (L _ (DataFamInstDecl (HsIB _ FamEqn {feqn_rhs=HsDataDefn {dd_ND=dn, dd_derivs=(L _ ds)}}))) = g dn ds idecl _ = mempty decl :: LHsDecl GhcPs -> Derives - decl (LL _ (TyClD _ (DataDecl _ _ _ _ HsDataDefn {dd_ND=dn, dd_derivs=(LL _ ds)}))) = g dn ds -- Data declaration. - decl (LL _ (DerivD _ (DerivDecl _ (HsWC _ sig) strategy _))) = addDerives Nothing (fmap unLoc strategy) [derivedToStr sig] -- A deriving declaration. + decl (L _ (TyClD _ (DataDecl _ _ _ _ HsDataDefn {dd_ND=dn, dd_derivs=(L _ ds)}))) = g dn ds -- Data declaration. + decl (L _ (DerivD _ (DerivDecl _ (HsWC _ sig) strategy _))) = addDerives Nothing (fmap unLoc strategy) [derivedToStr sig] -- A deriving declaration. decl _ = mempty g :: NewOrData -> [LHsDerivingClause GhcPs] -> Derives - g dn ds = mconcat [addDerives (Just dn) (fmap unLoc strategy) $ map derivedToStr tys | LL _ (HsDerivingClause _ strategy (LL _ tys)) <- ds] + g dn ds = mconcat [addDerives (Just dn) (fmap unLoc strategy) $ map derivedToStr tys | L _ (HsDerivingClause _ strategy (L _ tys)) <- ds] derivedToStr :: LHsSigType GhcPs -> String derivedToStr (HsIB _ t) = ih t where ih :: LHsType GhcPs -> String - ih (LL _ (HsQualTy _ _ a)) = ih a - ih (LL _ (HsParTy _ a)) = ih a - ih (LL _ (HsAppTy _ a _)) = ih a - ih (LL _ (HsTyVar _ _ a)) = unsafePrettyPrint $ unqual' a - ih (LL _ a) = unsafePrettyPrint a -- I don't anticipate this case is called. - ih _ = "" -- {-# COMPLETE LL #-} + ih (L _ (HsQualTy _ _ a)) = ih a + ih (L _ (HsParTy _ a)) = ih a + ih (L _ (HsAppTy _ a _)) = ih a + ih (L _ (HsTyVar _ _ a)) = unsafePrettyPrint $ unqual a + ih (L _ a) = unsafePrettyPrint a -- I don't anticipate this case is called. derivedToStr _ = "" -- new ctor -derives _ = mempty -- {-# COMPLETE LL #-} - un = undefined hasT t x = not $ null (universeBi x `asTypeOf` [t]) @@ -396,12 +517,3 @@ hasS :: (Data x, Data a) => (a -> Bool) -> x -> Bool hasS test = any test . universeBi - -has f = any f . universeBi - --- Only whole number fractions are permitted by NumDecimals extension. --- Anything not-whole raises an error. -isWholeFrac :: HsExpr GhcPs -> Bool -isWholeFrac (HsLit _ (HsRat _ (FL _ _ v) _)) = denominator v == 1 -isWholeFrac (HsOverLit _ (OverLit _ (HsFractional (FL _ _ v)) _)) = denominator v == 1 -isWholeFrac _ = False diff -Nru hlint-2.2.11/src/Hint/Import.hs hlint-3.1.6/src/Hint/Import.hs --- hlint-2.2.11/src/Hint/Import.hs 2020-02-07 10:36:33.000000000 +0000 +++ hlint-3.1.6/src/Hint/Import.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,4 +1,4 @@ -{-# LANGUAGE PatternGuards, ScopedTypeVariables, RecordWildCards #-} +{-# LANGUAGE LambdaCase, PatternGuards, RecordWildCards #-} {- Reduce the number of import declarations. Two import declarations can be combined if: @@ -28,34 +28,32 @@ import qualified A; import A import B; import A; import A -- import A import A hiding(Foo); import A hiding(Bar) -import List -- import Data.List -import qualified List -- import qualified Data.List as List -import Char(foo) -- import Data.Char(foo) -import IO(foo) -import IO as X -- import System.IO as X; import System.IO.Error as X; import Control.Exception as X (bracket,bracket_) +import A (foo) \ +import A (bar) \ +import A (baz) -- import A ( foo, bar, baz ) -} module Hint.Import(importHint) where -import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),suggest',toSS',rawIdea',rawIdeaN') +import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),suggest,toSS,rawIdea) import Refact.Types hiding (ModuleName) import qualified Refact.Types as R import Data.Tuple.Extra import Data.List.Extra -import Data.Generics.Uniplate.Operations +import Data.Generics.Uniplate.DataOnly import Data.Maybe import Control.Applicative import Prelude import FastString import BasicTypes -import RdrName -import Module -import HsSyn +import GHC.Hs import SrcLoc -import GHC.Util + +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable + importHint :: ModuHint importHint _ ModuleEx {ghcModule=L _ HsModule{hsmodImports=ms}} = @@ -67,15 +65,12 @@ , let n = unLoc $ ideclName i' , let pkg = unpackFS . sl_fs <$> ideclPkgQual i']) ++ -- Ideas for removing redundant 'as' clauses. - concatMap stripRedundantAlias ms ++ - -- Ideas for replacing deprecated imports by their preferred - -- equivalents. - concatMap preferHierarchicalImports ms + concatMap stripRedundantAlias ms reduceImports :: [LImportDecl GhcPs] -> [Idea] reduceImports [] = [] reduceImports ms@(m:_) = - [rawIdea' Hint.Type.Warning "Use fewer imports" (getLoc m) (f ms) (Just $ f x) [] rs + [rawIdea Hint.Type.Warning "Use fewer imports" (getLoc m) (f ms) (Just $ f x) [] rs | Just (x, rs) <- [simplify ms]] where f = unlines . map unsafePrettyPrint @@ -84,7 +79,9 @@ simplify [] = Nothing simplify (x : xs) = case simplifyHead x xs of Nothing -> first (x:) <$> simplify xs - Just (xs, rs) -> Just $ maybe (xs, rs) (second (++ rs)) $ simplify xs + Just (xs, rs) -> + let deletions = filter (\case Delete{} -> True; _ -> False) rs + in Just $ maybe (xs, rs) (second (++ deletions)) $ simplify xs simplifyHead :: LImportDecl GhcPs -> [LImportDecl GhcPs] @@ -97,27 +94,27 @@ combine :: LImportDecl GhcPs -> LImportDecl GhcPs -> Maybe (LImportDecl GhcPs, [Refactoring R.SrcSpan]) -combine x@(LL _ x') y@(LL _ y') +combine x@(L loc x') y@(L _ y') -- Both (un/)qualified, common 'as', same names : Delete the second. - | qual, as, specs = Just (x, [Delete Import (toSS' y)]) + | qual, as, specs = Just (x, [Delete Import (toSS y)]) -- Both (un/)qualified, common 'as', different names : Merge the -- second into the first and delete it. | qual, as , Just (False, xs) <- ideclHiding x' , Just (False, ys) <- ideclHiding y' = - let newImp = noLoc x'{ideclHiding = Just (False, noLoc (unLoc xs ++ unLoc ys))} - in Just (newImp, [Replace Import (toSS' x) [] (unsafePrettyPrint (unLoc newImp)) - , Delete Import (toSS' y)]) + let newImp = L loc x'{ideclHiding = Just (False, noLoc (unLoc xs ++ unLoc ys))} + in Just (newImp, [Replace Import (toSS x) [] (unsafePrettyPrint (unLoc newImp)) + , Delete Import (toSS y)]) -- Both (un/qualified), common 'as', one has names the other doesn't -- : Delete the one with names. | qual, as, isNothing (ideclHiding x') || isNothing (ideclHiding y') = let (newImp, toDelete) = if isNothing (ideclHiding x') then (x, y) else (y, x) - in Just (newImp, [Delete Import (toSS' toDelete)]) + in Just (newImp, [Delete Import (toSS toDelete)]) -- Both unqualified, same names, one (and only one) has an 'as' -- clause : Delete the one without an 'as'. - | not (ideclQualified x'), qual, specs, length ass == 1 = + | ideclQualified x' == NotQualified, qual, specs, length ass == 1 = let (newImp, toDelete) = if isJust (ideclAs x') then (x, y) else (y, x) - in Just (newImp, [Delete Import (toSS' toDelete)]) + in Just (newImp, [Delete Import (toSS toDelete)]) -- No hints. | otherwise = Nothing where @@ -131,59 +128,10 @@ ass = mapMaybe ideclAs [x', y'] specs = transformBi (const noSrcSpan) (ideclHiding x') == transformBi (const noSrcSpan) (ideclHiding y') -combine _ _ = Nothing -- {-# COMPLETE LL #-} stripRedundantAlias :: LImportDecl GhcPs -> [Idea] -stripRedundantAlias x@(LL loc i@ImportDecl {..}) +stripRedundantAlias x@(L loc i@ImportDecl {..}) -- Suggest 'import M as M' be just 'import M'. | Just (unLoc ideclName) == fmap unLoc ideclAs = - [suggest' "Redundant as" x (cL loc i{ideclAs=Nothing} :: LImportDecl GhcPs) [RemoveAsKeyword (toSS' x)]] + [suggest "Redundant as" x (cL loc i{ideclAs=Nothing} :: LImportDecl GhcPs) [RemoveAsKeyword (toSS x)]] stripRedundantAlias _ = [] - -preferHierarchicalImports :: LImportDecl GhcPs -> [Idea] -preferHierarchicalImports x@(LL loc i@ImportDecl{ideclName=L _ n,ideclPkgQual=Nothing}) - -- Suggest 'import IO' be rewritten 'import System.IO, import - -- System.IO.Error, import Control.Exception(bracket, bracket_)'. - | n == mkModuleName "IO" && isNothing (ideclHiding i) = - [rawIdeaN' Suggestion "Use hierarchical imports" loc - (trimStart $ unsafePrettyPrint i) ( - Just $ unlines $ map (trimStart . unsafePrettyPrint) - [ f "System.IO" Nothing, f "System.IO.Error" Nothing - , f "Control.Exception" $ Just (False, noLoc [mkLIE x | x <- ["bracket","bracket_"]])]) []] - -- Suggest that a module import like 'Monad' should be rewritten with - -- its hiearchical equivalent e.g. 'Control.Monad'. - | Just y <- lookup (moduleNameString n) newNames = - let newModuleName = y ++ "." ++ moduleNameString n - r = [Replace R.ModuleName (toSS' x) [] newModuleName] in - [suggest' "Use hierarchical imports" - x (noLoc (desugarQual i){ideclName=noLoc (mkModuleName newModuleName)} :: LImportDecl GhcPs) r] - where - -- Substitute a new module name. - f a b = (desugarQual i){ideclName=noLoc (mkModuleName a), ideclHiding=b} - -- Wrap a literal name into an 'IE' (import/export) value. - mkLIE :: String -> LIE GhcPs - mkLIE n = noLoc $ IEVar noExt (noLoc (IEName (noLoc (mkVarUnqual (fsLit n))))) - -- Rewrite 'import qualified X' as 'import qualified X as X'. - desugarQual :: ImportDecl GhcPs -> ImportDecl GhcPs - desugarQual i - | ideclQualified i && isNothing (ideclAs i) = i{ideclAs = Just (ideclName i)} - | otherwise = i - -preferHierarchicalImports _ = [] - -newNames :: [(String, String)] -newNames = let (*) = flip (,) in - ["Control" * "Monad" - ,"Data" * "Char" - ,"Data" * "List" - ,"Data" * "Maybe" - ,"Data" * "Ratio" - ,"System" * "Directory" - - -- Special, see bug https://code.google.com/archive/p/ndmitchell/issues/393 - -- ,"System" * "IO" - - -- Do not encourage use of old-locale/old-time over haskell98 - -- ,"System" * "Locale" - -- ,"System" * "Time" - ] diff -Nru hlint-2.2.11/src/Hint/Lambda.hs hlint-3.1.6/src/Hint/Lambda.hs --- hlint-2.2.11/src/Hint/Lambda.hs 2020-01-21 17:11:43.000000000 +0000 +++ hlint-3.1.6/src/Hint/Lambda.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,4 +1,4 @@ -{-# LANGUAGE ViewPatterns, PatternGuards #-} +{-# LANGUAGE LambdaCase, PatternGuards, ViewPatterns #-} {- Concept: @@ -8,7 +8,7 @@ Rules: fun a = \x -> y -- promote lambdas, provided no where's outside the lambda fun x = y x -- eta reduce, x /= mr and foo /= symbol - \x -> y x -- eta reduce + \x -> y x ==> y -- eta reduce ((#) x) ==> (x #) -- rotate operators (flip op x) ==> (`op` x) -- rotate operators \x y -> x + y ==> (+) -- insert operator @@ -22,37 +22,41 @@ f a = \x -> x + x -- f a x = x + x f a = \a -> a + a -- f _ a = a + a +a = \x -> x + x -- a x = x + x +f (Just a) = \a -> a + a -- f (Just _) a = a + a +f (Foo a b c) = \c -> c + c -- f (Foo a b _) c = c + c f a = \x -> x + x where _ = test f (test -> a) = \x -> x + x f = \x -> x + x -- f x = x + x -fun x y z = f x y z -- fun = f -fun x y z = f x x y z -- fun x = f x x -fun x y z = f g z -- fun x y = f g -fun mr = y mr -fun x = f . g $ x -- fun = f . g +fun x y z = f x y z -- fun = f @NoRefactor: refactoring for eta reduce is not implemented +fun x y z = f x x y z -- fun x = f x x @NoRefactor +fun x y z = f g z -- fun x y = f g @NoRefactor +fun x = f . g $ x -- fun = f . g @NoRefactor f = foo (\y -> g x . h $ y) -- g x . h f = foo (\y -> g x . h $ y) -- @Message Avoid lambda -f = foo ((*) x) -- (x *) +f = foo ((*) x) -- (x *) @NoRefactor f = (*) x -f = foo (flip op x) -- (`op` x) -f = foo (flip op x) -- @Message Use section +f = foo (flip op x) -- (`op` x) @NoRefactor +f = foo (flip op x) -- @Message Use section @NoRefactor foo x = bar (\ d -> search d table) -- (`search` table) foo x = bar (\ d -> search d table) -- @Message Avoid lambda using `infix` f = flip op x -f = foo (flip (*) x) -- (* x) +f = foo (flip (*) x) -- (* x) @NoRefactor f = foo (flip (-) x) f = foo (\x y -> fun x y) -- @Warning fun +f = foo (\x y z -> fun x y z) -- @Warning fun +f = foo (\z -> f x $ z) -- f x f = foo (\x y -> x + y) -- (+) f = foo (\x -> x * y) -- @Suggestion (* y) f = foo (\x -> x # y) f = foo (\x -> \y -> x x y y) -- \x y -> x x y y f = foo (\x -> \x -> foo x x) -- \_ x -> foo x x f = foo (\(foo -> x) -> \y -> x x y y) -f = foo (\(x:xs) -> \x -> foo x x) -- \(_:xs) x -> foo x x +f = foo (\(x:xs) -> \x -> foo x x) -- \(_:xs) x -> foo x x @NoRefactor f = foo (\x -> \y -> \z -> x x y y z z) -- \x y z -> x x y y z z x ! y = fromJust $ lookup x y f = foo (\i -> writeIdea (getClass i) i) -f = bar (flip Foo.bar x) -- (`Foo.bar` x) +f = bar (flip Foo.bar x) -- (`Foo.bar` x) @NoRefactor f = a b (\x -> c x d) -- (`c` d) yes = \x -> a x where -- a yes = \x y -> op y x where -- flip op @@ -65,117 +69,234 @@ foo = [\column -> set column [treeViewColumnTitle := printf "%s (match %d)" name (length candidnates)]] foo = [\x -> x] foo = [\m x -> insert x x m] -foo a b c = bar (flux ++ quux) c where flux = a -- foo a b = bar (flux ++ quux) +foo a b c = bar (flux ++ quux) c where flux = a -- foo a b = bar (flux ++ quux) @NoRefactor foo a b c = bar (flux ++ quux) c where flux = c yes = foo (\x -> Just x) -- @Warning Just foo = bar (\x -> (x `f`)) -- f -baz = bar (\x -> (x +)) -- (+) -foo = bar (\x -> case x of Y z -> z) -- \(Y z) -> z -yes = blah (\ x -> case x of A -> a; B -> b) -- \ case A -> a; B -> b +foo = bar (\x -> shakeRoot "src" x) +baz = bar (\x -> (x +)) -- (+) @NoRefactor +xs `withArgsFrom` args = f args +foo = bar (\x -> case x of Y z -> z) -- \(Y z) -> z @NoRefactor +yes = blah (\ x -> case x of A -> a; B -> b) -- \ case A -> a; B -> b @NoRefactor +yes = blah (\ x -> case x of A -> a; B -> b) -- @Note may require `{-# LANGUAGE LambdaCase #-}` adding to the top of the file @NoRefactor no = blah (\ x -> case x of A -> a x; B -> b x) -yes = blah (\ x -> (y, x, z+q)) -- (y, , z+q) +yes = blah (\ x -> (y, x)) -- (y,) @NoRefactor +yes = blah (\ x -> (y, x, z+q)) -- (y, , z+q) @NoRefactor +yes = blah (\ x -> (y, x, y, u, v)) -- (y, , y, u, v) @NoRefactor +yes = blah (\ x -> (y, x, z+q)) -- @Note may require `{-# LANGUAGE TupleSections #-}` adding to the top of the file @NoRefactor yes = blah (\ x -> (y, x, z+x)) tmp = map (\ x -> runST $ action x) yes = map (\f -> dataDir f) dataFiles -- (dataDir ) {-# LANGUAGE TypeApplications #-}; noBug545 = coerce ((<>) @[a]) {-# LANGUAGE QuasiQuotes #-}; authOAuth2 name = authOAuth2Widget [whamlet|Login via #{name}|] name {-# LANGUAGE QuasiQuotes #-}; authOAuth2 = foo (\name -> authOAuth2Widget [whamlet|Login via #{name}|] name) +f = {- generates a hint using hlint.yaml only -} map (flip (,) "a") "123" +f = {- generates a hint using hlint.yaml only -} map ((,) "a") "123" +f = map (\s -> MkFoo s 0 s) ["a","b","c"] -} module Hint.Lambda(lambdaHint) where -import Hint.Util -import Hint.Type +import Hint.Type (DeclHint, Idea, Note(RequiresExtension), suggest, warn, toSS, suggestN, ideaNote) import Util import Data.List.Extra -import Data.Maybe +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 :: Decl_ -> [Idea] -lambdaDecl (toFunBind -> o@(FunBind loc1 [Match _ name pats (UnGuardedRhs loc2 bod) bind])) - | isNothing bind, isLambda $ fromParen bod, null (universeBi pats :: [Exp_]) = - [warn "Redundant lambda" o (gen pats bod) [Replace Decl (toSS o) s1 t1]] +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]) + = [warn "Redundant lambda" o (gen pats origBody) [Replace Decl (toSS o) subts template]] | length pats2 < length pats, pvars (drop (length pats2) pats) `disjoint` varss bind - = [warn "Eta reduce" (reform pats bod) (reform pats2 bod2) - [ -- Disabled, see apply-refact #3 - -- Replace Decl (toSS $ reform pats bod) s2 t2]] - ]] - where reform p b = FunBind loc [Match an name p (UnGuardedRhs an b) Nothing] - loc = setSpanInfoEnd loc1 $ srcSpanEnd $ srcInfoSpan loc2 - gen ps = uncurry reform . fromLambda . Lambda an ps - (finalpats, body) = fromLambda . Lambda an pats $ bod - (pats2, bod2) = etaReduce pats bod - template fps b = prettyPrint $ reform (zipWith munge ['a'..'z'] fps) (toNamed "body") - munge :: Char -> Pat_ -> Pat_ - munge ident p@(PWildCard _) = p - munge ident p = PVar (ann p) (Ident (ann p) [ident]) - subts fps b = ("body", toSS b) : zipWith (\x y -> ([x],y)) ['a'..'z'] (map toSS fps) - s1 = subts finalpats body - --s2 = subts pats2 bod2 - t1 = template finalpats body - --t2 = template pats2 bod2 - + = [warn "Eta reduce" (reform pats origBody) (reform pats2 bod2) + [ -- Disabled, see apply-refact #3 + ] + ] + where reform :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsDecl GhcPs + reform ps b = L loc $ ValD noExtField $ + origBind + {fun_matches = MG noExtField (noLoc [noLoc $ Match noExtField ctxt ps $ GRHSs noExtField [noLoc $ GRHS noExtField [] b] $ noLoc $ EmptyLocalBinds noExtField]) Generated} + + loc = combineSrcSpans loc1 loc2 + + gen :: [LPat GhcPs] -> LHsExpr GhcPs -> LHsDecl GhcPs + gen ps = uncurry reform . fromLambda . lambda ps + + (finalpats, body) = fromLambda . lambda pats $ origBody + (pats2, bod2) = etaReduce pats origBody + (origPats, subtsVars) = mkOrigPats (Just (rdrNameStr funName)) finalpats + subts = ("body", toSS body) : zipWith (\x y -> ([x],y)) subtsVars (map toSS finalpats) + template = unsafePrettyPrint (reform origPats varBody) lambdaDecl _ = [] -setSpanInfoEnd ssi (line, col) = ssi{srcInfoSpan = (srcInfoSpan ssi){srcSpanEndLine=line, srcSpanEndColumn=col}} - -etaReduce :: [Pat_] -> Exp_ -> ([Pat_], Exp_) -etaReduce ps (App _ x (Var _ (UnQual _ (Ident _ y)))) - | ps /= [], PVar _ (Ident _ p) <- last ps, p == y, p /= "mr", y `notElem` vars x +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 (init ps) x -etaReduce ps (InfixApp a x (isDol -> True) y) = etaReduce ps (App a x y) -etaReduce ps x = (ps,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) --Section refactoring is not currently implemented. -lambdaExp :: Maybe Exp_ -> Exp_ -> [Idea] -lambdaExp p o@(Paren _ (App _ v@(Var l (UnQual _ (Symbol _ x))) y)) | isAtom y, not $ isTypeApp y, allowLeftSection x = - [suggestN "Use section" o (exp y x)] -- [Replace Expr (toSS o) subts template]] +lambdaExp :: Maybe (LHsExpr GhcPs) -> LHsExpr GhcPs -> [Idea] +lambdaExp _ o@(L _ (HsPar _ (L _ (HsApp _ oper@(L _ (HsVar _ (L _ (rdrNameOcc -> f)))) y)))) + | isSymOcc f -- is this an operator? + , isAtom y + , allowLeftSection $ occNameString f + , not $ isTypeApp y = + [suggestN "Use section" o $ noLoc $ HsPar noExtField $ noLoc $ SectionL noExtField y oper] + +lambdaExp _ o@(L _ (HsPar _ (view -> App2 (view -> Var_ "flip") origf@(view -> Var_ f) y))) + | allowRightSection f, not $ "(" `isPrefixOf` f + = [suggestN "Use section" o $ noLoc $ HsPar noExtField $ noLoc $ SectionR noExtField origf y] +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 "") + -- If the lambda's parent is an HsPar, and the result is also an HsPar, the span should include the parentheses. + , let from = case (p, res) of + (Just p@(L _ (HsPar _ (L _ HsLam{}))), L _ HsPar{}) -> p + _ -> o + = [(if isVar res then warn else suggest) name from res (refact $ toSS from)] where - exp op rhs = LeftSection an op (toNamed rhs) --- template = prettyPrint (exp (toNamed "a") "*") --- subts = [("a", toSS y), ("*", toSS v)] -lambdaExp p o@(Paren _ (App _ (App _ (view -> Var_ "flip") (Var _ x)) y)) | allowRightSection $ fromNamed x = - [suggestN "Use section" o $ RightSection an (QVarOp an x) y] -lambdaExp p o@Lambda{} - | maybe True (not . isInfixApp) p, (res, refact) <- niceLambdaR [] o - , not $ isLambda res, not $ any isQuasiQuote $ universe res, not $ "runST" `Set.member` freeVars o - , let name = "Avoid lambda" ++ (if countInfixNames res > countInfixNames o then " using `infix`" else "") = - [(if isVar res || isCon res then warn else suggest) name o res (refact $ toSS o)] - where countInfixNames x = length [() | RightSection _ (QVarOp _ (UnQual _ (Ident _ _))) _ <- universe x] -lambdaExp p o@(Lambda _ pats x) | isLambda (fromParen x), null (universeBi pats :: [Exp_]), maybe True (not . isLambda) p = - [suggest "Collapse lambdas" o (Lambda an pats body) [Replace Expr (toSS o) subts template]] + 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]) -- TODO: I think this checks for view patterns only, so maybe be more explicit about that? + , maybe True (not . isLambda) p = + [suggest "Collapse lambdas" o (lambda pats body) [Replace Expr (toSS o) subts template]] where (pats, body) = fromLambda o - template = prettyPrint $ Lambda an (zipWith munge ['a'..'z'] pats) (toNamed "body") - munge :: Char -> Pat_ -> Pat_ - munge ident p@(PWildCard _) = p - munge ident p = PVar (ann p) (Ident (ann p) [ident]) - subts = ("body", toSS body) : zipWith (\x y -> ([x],y)) ['a'..'z'] (map toSS pats) -lambdaExp p o@(Lambda _ [view -> PVar_ u] (Case _ (view -> Var_ v) alts)) - | u == v, u `notElem` vars alts = case alts of - [Alt _ pat (UnGuardedRhs _ bod) Nothing] -> [suggestN "Use lambda" o $ Lambda an [pat] bod] - _ -> [(suggestN "Use lambda-case" o $ LCase an alts){ideaNote=[RequiresExtension "LambdaCase"]}] -lambdaExp p o@(Lambda _ [view -> PVar_ u] (Tuple _ boxed xs)) - | ([yes],no) <- partition (~= u) xs, u `notElem` concatMap vars no - = [(suggestN "Use tuple-section" o $ TupleSection an boxed [if x ~= u then Nothing else Just x | x <- xs]) - {ideaNote=[RequiresExtension "TupleSections"]}] + (oPats, subtsVars) = mkOrigPats Nothing pats + subts = ("body", toSS body) : zipWith (\x y -> ([x],y)) subtsVars (map toSS pats) + template = unsafePrettyPrint (lambda oPats varBody) + +-- match a lambda with a variable pattern, with no guards and no where clauses +lambdaExp _ o@(SimpleLambda [view -> PVar_ x] (L _ expr)) = + case expr of + -- suggest TupleSections instead of lambdas + ExplicitTuple _ args boxity + -- is there exactly one argument that is exactly x? + | ([_x], ys) <- partition ((==Just x) . tupArgVar) args + -- the other arguments must not have a nested x somewhere in them + , Set.notMember x $ Set.map occNameString $ freeVars ys + -> [(suggestN "Use tuple-section" o $ noLoc $ ExplicitTuple noExtField (map removeX args) boxity) + {ideaNote = [RequiresExtension "TupleSections"]}] + + -- suggest @LambdaCase@/directly matching in a lambda instead of doing @\x -> case x of ...@ + HsCase _ (view -> Var_ x') matchGroup + -- is the case being done on the variable from our original lambda? + | x == x' + -- x must not be used in some other way inside the matches + , Set.notMember x $ Set.map occNameString $ free $ allVars matchGroup + -> case matchGroup of + -- is there a single match? - suggest match inside the lambda + -- + -- we need to + -- * add brackets to the match, because matches in lambdas require them + -- * mark match as being in a lambda context so that it's printed properly + 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 + } + ] } + ] + + -- otherwise we should use @LambdaCase@ + MG _ (L _ xs) _ -> + [(suggestN "Use lambda-case" o $ noLoc $ HsLamCase noExtField matchGroup) + {ideaNote=[RequiresExtension "LambdaCase"]}] + _ -> [] + _ -> [] + where + -- | Filter out tuple arguments, converting the @x@ (matched in the lambda) variable argument + -- to a missing argument, so that we get the proper section. + removeX :: LHsTupArg GhcPs -> LHsTupArg GhcPs + removeX arg@(L _ (Present _ (view -> Var_ x'))) + | x == x' = noLoc $ Missing noExtField + removeX y = y + -- | Extract the name of an argument of a tuple if it's present and a variable. + tupArgVar :: LHsTupArg GhcPs -> Maybe String + tupArgVar (L _ (Present _ (view -> Var_ x))) = Just x + tupArgVar _ = Nothing + lambdaExp _ _ = [] +varBody :: LHsExpr GhcPs +varBody = strToVar "body" --- replace any repeated pattern variable with _ -fromLambda :: Exp_ -> ([Pat_], Exp_) -fromLambda (Lambda _ ps1 (fromLambda . fromParen -> (ps2,x))) = (transformBi (f $ pvars ps2) ps1 ++ ps2, x) - where f bad x@PVar{} | prettyPrint x `elem` bad = PWildCard an +-- | Squash lambdas and replace any repeated pattern variable with @_@ +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) + +-- | For each pattern, if it does not contain wildcards, replace it with a variable pattern. +-- +-- The second component of the result is a list of substitution variables, which is ['a'..'z'], +-- excluding variables that occur in the function name or patterns with wildcards. For example, given +-- 'f (Foo a b _) = ...', 'f', 'a' and 'b' are removed. +mkOrigPats :: Maybe String -> [LPat GhcPs] -> ([LPat GhcPs], [Char]) +mkOrigPats funName pats = (zipWith munge subtsVars pats', subtsVars) + where + (Set.unions -> used, pats') = unzip (map f pats) + + -- Remove variables that occur in the function name or patterns with wildcards + subtsVars = filter (\c -> c `Set.notMember` used && Just [c] /= funName) ['a'..'z'] + + -- Returns (chars in the pattern if the pattern contains wildcards, (whether the pattern contains wildcards, the pattern)) + f :: LPat GhcPs -> (Set Char, (Bool, LPat GhcPs)) + f p + | any isWildPat (universe p) = + let used = Set.fromList [c | (L _ (VarPat _ (rdrNameStr -> [c]))) <- universe p] + in (used, (True, p)) + | otherwise = (mempty, (False, p)) + + isWildPat :: LPat GhcPs -> Bool + isWildPat = \case (L _ (WildPat _)) -> True; _ -> False + + -- Replace the pattern with a variable pattern if the pattern doesn't contain wildcards. + munge :: Char -> (Bool, LPat GhcPs) -> LPat GhcPs + munge _ (True, p) = p + munge ident (False, L ploc _) = L ploc (VarPat noExtField (L ploc $ mkRdrUnqual $ mkVarOcc [ident])) diff -Nru hlint-2.2.11/src/Hint/List.hs hlint-3.1.6/src/Hint/List.hs --- hlint-2.2.11/src/Hint/List.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/Hint/List.hs 2020-06-14 18:45:05.000000000 +0000 @@ -32,36 +32,41 @@ issue619 = [pkgJobs | Pkg{pkgGpd, pkgJobs} <- pkgs, not $ null $ C.condTestSuites pkgGpd] {-# LANGUAGE MonadComprehensions #-}\ foo = [x | False, x <- [1 .. 10]] -- [] +foo = [_ | x <- _, let _ = A{x}] +issue1039 = foo (map f [1 | _ <- []]) -- [f 1 | _ <- []] -} module Hint.List(listHint) where import Control.Applicative -import Data.Generics.Uniplate.Operations +import Data.Generics.Uniplate.DataOnly import Data.List.Extra import Data.Maybe import Prelude -import Hint.Type(DeclHint',Idea,suggest',toSS') +import Hint.Type(DeclHint,Idea,suggest,ignore,toRefactSrcSpan,toSS) import Refact.Types hiding (SrcSpan) import qualified Refact.Types as R -import HsSyn +import GHC.Hs import SrcLoc import BasicTypes import RdrName -import OccName import Name import FastString import TysWiredIn import GHC.Util +import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr +import Language.Haskell.GhclibParserEx.GHC.Hs.Types import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader -listHint :: DeclHint' +listHint :: DeclHint listHint _ _ = listDecl listDecl :: LHsDecl GhcPs -> [Idea] @@ -75,180 +80,186 @@ -- structure of 'listComp'. listComp :: LHsExpr GhcPs -> [Idea] -listComp o@(LL _ (HsDo _ ListComp (L _ stmts))) = +listComp o@(L _ (HsDo _ ListComp (L _ stmts))) = listCompCheckGuards o ListComp stmts -listComp o@(LL _ (HsDo _ MonadComp (L _ stmts))) = +listComp o@(L _ (HsDo _ MonadComp (L _ stmts))) = listCompCheckGuards o MonadComp stmts -listComp o@(view' -> App2' mp f (LL _ (HsDo _ ListComp (L _ stmts)))) = +listComp (L _ HsPar{}) = [] -- App2 "sees through" paren, which causes duplicate hints with universeBi +listComp o@(view -> App2 mp f (L _ (HsDo _ ListComp (L _ stmts)))) = listCompCheckMap o mp f ListComp stmts -listComp o@(view' -> App2' mp f (LL _ (HsDo _ MonadComp (L _ stmts)))) = +listComp o@(view -> App2 mp f (L _ (HsDo _ MonadComp (L _ stmts)))) = listCompCheckMap o mp f MonadComp stmts listComp _ = [] listCompCheckGuards :: LHsExpr GhcPs -> HsStmtContext Name -> [ExprLStmt GhcPs] -> [Idea] listCompCheckGuards o ctx stmts = let revs = reverse stmts - e@(LL _ LastStmt{}) = head revs -- In a ListComp, this is always last. + e@(L _ LastStmt{}) = head revs -- In a ListComp, this is always last. xs = reverse (tail revs) in list_comp_aux e xs where list_comp_aux e xs - | "False" `elem` cons = [suggest' "Short-circuited list comprehension" o o' (suggestExpr o o')] - | "True" `elem` cons = [suggest' "Redundant True guards" o o2 (suggestExpr o o2)] - | not (astListEq xs ys) = [suggest' "Move guards forward" o o3 (suggestExpr o o3)] + | "False" `elem` cons = [suggest "Short-circuited list comprehension" o o' (suggestExpr o o')] + | "True" `elem` cons = [suggest "Redundant True guards" o o2 (suggestExpr o o2)] + | not (astListEq xs ys) = [suggest "Move guards forward" o o3 (suggestExpr o o3)] | otherwise = [] where ys = moveGuardsForward xs - o' = noLoc $ ExplicitList noExt Nothing [] - o2 = noLoc $ HsDo noExt ctx (noLoc (filter ((/= Just "True") . qualCon) xs ++ [e])) - o3 = noLoc $ HsDo noExt ctx (noLoc $ ys ++ [e]) + o' = noLoc $ ExplicitList noExtField Nothing [] + o2 = noLoc $ HsDo noExtField ctx (noLoc (filter ((/= Just "True") . qualCon) xs ++ [e])) + o3 = noLoc $ HsDo noExtField ctx (noLoc $ ys ++ [e]) cons = mapMaybe qualCon xs qualCon :: ExprLStmt GhcPs -> Maybe String - qualCon (L _ (BodyStmt _ (LL _ (HsVar _ (L _ x))) _ _)) = Just (occNameString . rdrNameOcc $ x) + qualCon (L _ (BodyStmt _ (L _ (HsVar _ (L _ x))) _ _)) = Just (occNameStr x) qualCon _ = Nothing listCompCheckMap :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs -> HsStmtContext Name -> [ExprLStmt GhcPs] -> [Idea] listCompCheckMap o mp f ctx stmts | varToStr mp == "map" = - [suggest' "Move map inside list comprehension" o o2 (suggestExpr o o2)] + [suggest "Move map inside list comprehension" o o2 (suggestExpr o o2)] where revs = reverse stmts - LL _ (LastStmt _ body b s) = head revs -- In a ListComp, this is always last. - last = noLoc $ LastStmt noExt (noLoc $ HsApp noExt (paren' f) (paren' body)) b s - o2 =noLoc $ HsDo noExt ctx (noLoc $ reverse (tail revs) ++ [last]) + L _ (LastStmt _ body b s) = head revs -- In a ListComp, this is always last. + last = noLoc $ LastStmt noExtField (noLoc $ HsApp noExtField (paren f) (paren body)) b s + o2 =noLoc $ HsDo noExtField ctx (noLoc $ reverse (tail revs) ++ [last]) listCompCheckMap _ _ _ _ _ = [] suggestExpr :: LHsExpr GhcPs -> LHsExpr GhcPs -> [Refactoring R.SrcSpan] -suggestExpr o o2 = [Replace Expr (toSS' o) [] (unsafePrettyPrint o2)] +suggestExpr o o2 = [Replace Expr (toSS o) [] (unsafePrettyPrint o2)] moveGuardsForward :: [ExprLStmt GhcPs] -> [ExprLStmt GhcPs] moveGuardsForward = reverse . f [] . reverse where f guards (x@(L _ (BindStmt _ p _ _ _)) : xs) = reverse stop ++ x : f move xs where (move, stop) = - span (if any hasPFieldsDotDot' (universeBi x) - || any isPFieldWildcard' (universeBi x) + span (if any hasPFieldsDotDot (universeBi x) + || any isPFieldWildcard (universeBi x) then const False - else \x -> pvars' p `disjoint` vars_ x) guards + else \x -> + let pvs = pvars p in + -- See this code from 'RdrHsSyn.hs' (8.10.1): + -- plus_RDR, pun_RDR :: RdrName + -- plus_RDR = mkUnqual varName (fsLit "+") -- Hack + -- pun_RDR = mkUnqual varName (fsLit "pun-right-hand-side") + -- Todo (SF, 2020-03-28): Try to make this better somehow. + pvs `disjoint` varss x && "pun-right-hand-side" `notElem` pvs + ) guards f guards (x@(L _ BodyStmt{}):xs) = f (x:guards) xs f guards (x@(L _ LetStmt{}):xs) = f (x:guards) xs f guards xs = reverse guards ++ xs - -- Fake something that works - vars_ x = [unsafePrettyPrint a | HsVar _ (LL _ a) <- universeBi x :: [HsExpr GhcPs]] - listExp :: Bool -> LHsExpr GhcPs -> [Idea] -listExp b (fromParen' -> x) = +listExp b (fromParen -> x) = if null res then concatMap (listExp $ isAppend x) $ children x else [head res] where - res = [suggest' name x x2 [r] + res = [suggest name x x2 [r] | (name, f) <- checks , Just (x2, subts, temp) <- [f b x] - , let r = Replace Expr (toSS' x) subts temp ] + , let r = Replace Expr (toSS x) subts temp ] -listPat :: Pat GhcPs -> [Idea] +listPat :: LPat GhcPs -> [Idea] listPat x = if null res then concatMap listPat $ children x else [head res] - where res = [suggest' name x x2 [r] + where res = [suggest name x x2 [r] | (name, f) <- pchecks , Just (x2, subts, temp) <- [f x] - , let r = Replace Pattern (toSS' x) subts temp ] -isAppend :: View' a App2' => a -> Bool -isAppend (view' -> App2' op _ _) = varToStr op == "++" + , let r = Replace Pattern (toSS x) subts temp ] +isAppend :: View a App2 => a -> Bool +isAppend (view -> App2 op _ _) = varToStr op == "++" isAppend _ = False checks ::[(String, Bool -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String))] -checks = let (*) = (,) in drop 1 -- see #174 +checks = let (*) = (,) in drop1 -- see #174 [ "Use string literal" * useString , "Use list literal" * useList , "Use :" * useCons ] -pchecks :: [(String, Pat GhcPs -> Maybe (Pat GhcPs, [(String, R.SrcSpan)], String))] -pchecks = let (*) = (,) in drop 1 -- see #174 +pchecks :: [(String, LPat GhcPs -> Maybe (LPat GhcPs, [(String, R.SrcSpan)], String))] +pchecks = let (*) = (,) in drop1 -- see #174 [ "Use string literal pattern" * usePString , "Use list literal pattern" * usePList ] -usePString :: Pat GhcPs -> Maybe (Pat GhcPs, [a], String) -usePString (LL _ (ListPat _ xs)) | not $ null xs, Just s <- mapM fromPChar' xs = - let literal = noLoc $ LitPat noExt (HsString NoSourceText (fsLit (show s))) +usePString :: LPat GhcPs -> Maybe (LPat GhcPs, [a], String) +usePString (L _ (ListPat _ xs)) | not $ null xs, Just s <- mapM fromPChar xs = + let literal = noLoc $ LitPat noExtField (HsString NoSourceText (fsLit (show s))) in Just (literal, [], unsafePrettyPrint literal) usePString _ = Nothing -usePList :: Pat GhcPs -> Maybe (Pat GhcPs, [(String, R.SrcSpan)], String) +usePList :: LPat GhcPs -> Maybe (LPat GhcPs, [(String, R.SrcSpan)], String) usePList = fmap ( (\(e, s) -> - (noLoc (ListPat noExt e) - , map (fmap toSS') s - , unsafePrettyPrint (noLoc $ ListPat noExt (map snd s) :: Pat GhcPs)) + (noLoc (ListPat noExtField e) + , map (fmap toRefactSrcSpan . fst) s + , unsafePrettyPrint (noLoc $ ListPat noExtField (map snd s) :: LPat GhcPs)) ) . unzip ) . f True ['a'..'z'] where - f first _ x | patToStr' x == "[]" = if first then Nothing else Just [] - f first (ident:cs) (view' -> PApp_' ":" [a, b]) = ((a, g ident a) :) <$> f False cs b + f first _ x | patToStr x == "[]" = if first then Nothing else Just [] + f first (ident:cs) (view -> PApp_ ":" [a, b]) = ((a, g ident a) :) <$> f False cs b f first _ _ = Nothing - g :: Char -> Pat GhcPs -> (String, Pat GhcPs) - g c p = ([c], VarPat noExt (noLoc $ mkVarUnqual (fsLit [c]))) + g :: Char -> LPat GhcPs -> ((String, SrcSpan), LPat GhcPs) + g c (getLoc -> loc) = (([c], loc), noLoc $ VarPat noExtField (noLoc $ mkVarUnqual (fsLit [c]))) useString :: p -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [a], String) -useString b (LL _ (ExplicitList _ _ xs)) | not $ null xs, Just s <- mapM fromChar xs = - let literal = noLoc (HsLit noExt (HsString NoSourceText (fsLit (show s)))) +useString b (L _ (ExplicitList _ _ xs)) | not $ null xs, Just s <- mapM fromChar xs = + let literal = noLoc (HsLit noExtField (HsString NoSourceText (fsLit (show s)))) in Just (literal, [], unsafePrettyPrint literal) useString _ _ = Nothing useList :: p -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String) useList b = fmap ( (\(e, s) -> - (noLoc (ExplicitList noExt Nothing e) - , map (fmap toSS') s - , unsafePrettyPrint (noLoc $ ExplicitList noExt Nothing (map snd s) :: LHsExpr GhcPs)) + (noLoc (ExplicitList noExtField Nothing e) + , map (fmap toSS) s + , unsafePrettyPrint (noLoc $ ExplicitList noExtField Nothing (map snd s) :: LHsExpr GhcPs)) ) . unzip ) . f True ['a'..'z'] where f first _ x | varToStr x == "[]" = if first then Nothing else Just [] - f first (ident:cs) (view' -> App2' c a b) | varToStr c == ":" = + f first (ident:cs) (view -> App2 c a b) | varToStr c == ":" = ((a, g ident a) :) <$> f False cs b f first _ _ = Nothing g :: Char -> LHsExpr GhcPs -> (String, LHsExpr GhcPs) - g c p = ([c], strToVar [c]) + g c p = ([c], L (getLoc p) (unLoc $ strToVar [c])) -useCons :: View' a App2' => Bool -> a -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String) -useCons False (view' -> App2' op x y) | varToStr op == "++" - , Just (x2, build) <- f x - , not $ isAppend y = - Just (gen (build x2) y - , [("x", toSS' x2), ("xs", toSS' y)] - , unsafePrettyPrint $ gen (build $ strToVar "x") (strToVar "xs") +useCons :: View a App2 => Bool -> a -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String) +useCons False (view -> App2 op x y) | varToStr op == "++" + , Just (newX, tplX, spanX) <- f x + , not $ isAppend y = + Just (gen newX y + , [("x", spanX), ("xs", toSS y)] + , unsafePrettyPrint $ gen tplX (strToVar "xs") ) where - f :: LHsExpr GhcPs -> - Maybe (LHsExpr GhcPs, LHsExpr GhcPs -> LHsExpr GhcPs) - f (LL _ (ExplicitList _ _ [x]))= - Just (x, \v -> if isApp x then v else paren' v) + f :: LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, LHsExpr GhcPs, R.SrcSpan) + f (L _ (ExplicitList _ _ [x])) + | isAtom x || isApp x = Just (x, strToVar "x", toSS x) + | otherwise = Just (addParen x, addParen (strToVar "x"), toSS x) f _ = Nothing gen :: LHsExpr GhcPs -> LHsExpr GhcPs -> LHsExpr GhcPs - gen x = noLoc . OpApp noExt x (noLoc (HsVar noExt (noLoc consDataCon_RDR))) + gen x = noLoc . OpApp noExtField x (noLoc (HsVar noExtField (noLoc consDataCon_RDR))) useCons _ _ = Nothing typeListChar :: LHsType GhcPs typeListChar = - noLoc $ HsListTy noExt - (noLoc (HsTyVar noExt NotPromoted (noLoc (mkVarUnqual (fsLit "Char"))))) + noLoc $ HsListTy noExtField + (noLoc (HsTyVar noExtField NotPromoted (noLoc (mkVarUnqual (fsLit "Char"))))) typeString :: LHsType GhcPs typeString = - noLoc $ HsTyVar noExt NotPromoted (noLoc (mkVarUnqual (fsLit "String"))) + noLoc $ HsTyVar noExtField NotPromoted (noLoc (mkVarUnqual (fsLit "String"))) stringType :: LHsDecl GhcPs -> [Idea] -stringType (LL _ x) = case x of +stringType (L _ x) = case x of InstD _ ClsInstD{ cid_inst= ClsInstDecl{cid_binds=x, cid_tyfam_insts=y, cid_datafam_insts=z}} -> @@ -258,8 +269,7 @@ f x = concatMap g $ childrenBi x g :: LHsType GhcPs -> [Idea] - g e@(fromTyParen' -> x) = [suggest' "Use String" x (transform f x) + g e@(fromTyParen -> x) = [ignore "Use String" x (transform f x) rs | not . null $ rs] where f x = if astEq x typeListChar then typeString else x - rs = [Replace Type (toSS' t) [] (unsafePrettyPrint typeString) | t <- universe x, astEq t typeListChar] -stringType _ = [] -- {-# COMPLETE LL #-} + rs = [Replace Type (toSS t) [] (unsafePrettyPrint typeString) | t <- universe x, astEq t typeListChar] diff -Nru hlint-2.2.11/src/Hint/ListRec.hs hlint-3.1.6/src/Hint/ListRec.hs --- hlint-2.2.11/src/Hint/ListRec.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/Hint/ListRec.hs 2020-06-14 18:45:05.000000000 +0000 @@ -31,9 +31,9 @@ module Hint.ListRec(listRecHint) where -import Hint.Type (DeclHint', Severity(Suggestion, Warning), idea', toSS') +import Hint.Type (DeclHint, Severity(Suggestion, Warning), idea, toSS) -import Data.Generics.Uniplate.Operations +import Data.Generics.Uniplate.DataOnly import Data.List.Extra import Data.Maybe import Data.Either.Extra @@ -41,22 +41,24 @@ import Refact.Types hiding (RType(Match)) import SrcLoc -import HsExtension -import HsPat -import HsTypes +import GHC.Hs.Extension +import GHC.Hs.Pat +import GHC.Hs.Types import TysWiredIn import RdrName -import HsBinds -import HsExpr -import HsDecls -import OccName +import GHC.Hs.Binds +import GHC.Hs.Expr +import GHC.Hs.Decls import BasicTypes import GHC.Util +import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader -listRecHint :: DeclHint' +listRecHint :: DeclHint listRecHint _ _ = concatMap f . universe where f o = maybeToList $ do @@ -64,10 +66,10 @@ (x, addCase) <- findCase x (use,severity,x) <- matchListRec x let y = addCase x - guard $ recursiveStr `notElem` varss' y + guard $ recursiveStr `notElem` varss y -- Maybe we can do better here maintaining source -- formatting? - return $ idea' severity ("Use " ++ use) o y [Replace Decl (toSS' o) [] (unsafePrettyPrint y)] + pure $ idea severity ("Use " ++ use) o y [Replace Decl (toSS o) [] (unsafePrettyPrint y)] recursiveStr :: String recursiveStr = "_recursive_" @@ -99,50 +101,50 @@ matchListRec :: ListCase -> Maybe (String, Severity, LHsExpr GhcPs) matchListRec o@(ListCase vs nil (x, xs, cons)) -- Suggest 'map'? - | [] <- vs, varToStr nil == "[]", (LL _ (OpApp _ lhs c rhs)) <- cons, varToStr c == ":" - , astEq (fromParen' rhs) recursive, xs `notElem` vars' lhs + | [] <- vs, varToStr nil == "[]", (L _ (OpApp _ lhs c rhs)) <- cons, varToStr c == ":" + , astEq (fromParen rhs) recursive, xs `notElem` vars lhs = Just $ (,,) "map" Hint.Type.Warning $ - appsBracket' [ strToVar "map", niceLambda' [x] lhs, strToVar xs] + appsBracket [ strToVar "map", niceLambda [x] lhs, strToVar xs] -- Suggest 'foldr'? - | [] <- vs, App2' op lhs rhs <- view' cons - , xs `notElem` (vars' op ++ vars' lhs) -- the meaning of xs changes, see #793 - , astEq (fromParen' rhs) recursive + | [] <- vs, App2 op lhs rhs <- view cons + , xs `notElem` (vars op ++ vars lhs) -- the meaning of xs changes, see #793 + , astEq (fromParen rhs) recursive = Just $ (,,) "foldr" Suggestion $ - appsBracket' [ strToVar "foldr", niceLambda' [x] $ appsBracket' [op,lhs], nil, strToVar xs] + appsBracket [ strToVar "foldr", niceLambda [x] $ appsBracket [op,lhs], nil, strToVar xs] -- Suggest 'foldl'? - | [v] <- vs, view' nil == Var_' v, (LL _ (HsApp _ r lhs)) <- cons - , astEq (fromParen' r) recursive - , xs `notElem` vars' lhs + | [v] <- vs, view nil == Var_ v, (L _ (HsApp _ r lhs)) <- cons + , astEq (fromParen r) recursive + , xs `notElem` vars lhs = Just $ (,,) "foldl" Suggestion $ - appsBracket' [ strToVar "foldl", niceLambda' [v,x] lhs, strToVar v, strToVar xs] + appsBracket [ strToVar "foldl", niceLambda [v,x] lhs, strToVar v, strToVar xs] -- Suggest 'foldM'? - | [v] <- vs, (LL _ (HsApp _ ret res)) <- nil, isReturn ret, varToStr res == "()" || view' res == Var_' v - , [LL _ (BindStmt _ (view' -> PVar_' b1) e _ _), LL _ (BodyStmt _ (fromParen' -> (LL _ (HsApp _ r (view' -> Var_' b2)))) _ _)] <- asDo cons - , b1 == b2, astEq r recursive, xs `notElem` vars' e + | [v] <- vs, (L _ (HsApp _ ret res)) <- nil, isReturn ret, varToStr res == "()" || view res == Var_ v + , [L _ (BindStmt _ (view -> PVar_ b1) e _ _), L _ (BodyStmt _ (fromParen -> (L _ (HsApp _ r (view -> Var_ b2)))) _ _)] <- asDo cons + , b1 == b2, astEq r recursive, xs `notElem` vars e , name <- "foldM" ++ ['_' | varToStr res == "()"] = Just $ (,,) name Suggestion $ - appsBracket' [strToVar name, niceLambda' [v,x] e, strToVar v, strToVar xs] + appsBracket [strToVar name, niceLambda [v,x] e, strToVar v, strToVar xs] -- Nope, I got nothing ¯\_(ツ)_/¯. | otherwise = Nothing -- Very limited attempt to convert >>= to do, only useful for -- 'foldM' / 'foldM_'. asDo :: LHsExpr GhcPs -> [LStmt GhcPs (LHsExpr GhcPs)] -asDo (view' -> - App2' bind lhs - (LL _ (HsLam _ MG { +asDo (view -> + App2 bind lhs + (L _ (HsLam _ MG { mg_origin=FromSource - , mg_alts=LL _ [ - LL _ Match { m_ctxt=LambdaExpr - , m_pats=[LL _ v@VarPat{}] + , mg_alts=L _ [ + L _ Match { m_ctxt=LambdaExpr + , m_pats=[v@(L _ VarPat{})] , m_grhss=GRHSs _ - [LL _ (GRHS _ [] rhs)] - (LL _ (EmptyLocalBinds _))}]})) + [L _ (GRHS _ [] rhs)] + (L _ (EmptyLocalBinds _))}]})) ) = - [ noLoc $ BindStmt noExt v lhs noSyntaxExpr noSyntaxExpr - , noLoc $ BodyStmt noExt rhs noSyntaxExpr noSyntaxExpr ] -asDo (LL _ (HsDo _ DoExpr (LL _ stmts))) = stmts -asDo x = [noLoc $ BodyStmt noExt x noSyntaxExpr noSyntaxExpr] + [ noLoc $ BindStmt noExtField v lhs noSyntaxExpr noSyntaxExpr + , noLoc $ BodyStmt noExtField rhs noSyntaxExpr noSyntaxExpr ] +asDo (L _ (HsDo _ DoExpr (L _ stmts))) = stmts +asDo x = [noLoc $ BodyStmt noExtField x noSyntaxExpr noSyntaxExpr] --------------------------------------------------------------------- @@ -152,46 +154,46 @@ findCase :: LHsDecl GhcPs -> Maybe (ListCase, LHsExpr GhcPs -> LHsDecl GhcPs) findCase x = do -- Match a function binding with two alternatives. - (LL _ (ValD _ FunBind {fun_matches= + (L _ (ValD _ FunBind {fun_matches= MG{mg_origin=FromSource, mg_alts= - (LL _ - [ x1@(LL _ Match{..}) -- Match fields. + (L _ + [ x1@(L _ Match{..}) -- Match fields. , x2]), ..} -- Match group fields. , ..} -- Fun. bind fields. - )) <- return x + )) <- pure x Branch name1 ps1 p1 c1 b1 <- findBranch x1 Branch name2 ps2 p2 c2 b2 <- findBranch x2 guard (name1 == name2 && ps1 == ps2 && p1 == p2) - [(BNil, b1), (BCons x xs, b2)] <- return $ sortOn fst [(c1, b1), (c2, b2)] - b2 <- transformAppsM' (delCons name1 p1 xs) b2 - (ps, b2) <- return $ eliminateArgs ps1 b2 - - let ps12 = let (a, b) = splitAt p1 ps1 in map strToPat' (a ++ xs : b) -- Function arguments. - emptyLocalBinds = noLoc $ EmptyLocalBinds noExt -- Empty where clause. - gRHS e = noLoc $ GRHS noExt [] e :: LGRHS GhcPs (LHsExpr GhcPs) -- Guarded rhs. - gRHSSs e = GRHSs noExt [gRHS e] emptyLocalBinds -- Guarded rhs set. - match e = Match{m_ext=noExt,m_pats=ps12, m_grhss=gRHSSs e, ..} -- Match. + [(BNil, b1), (BCons x xs, b2)] <- pure $ sortOn fst [(c1, b1), (c2, b2)] + b2 <- transformAppsM (delCons name1 p1 xs) b2 + (ps, b2) <- pure $ eliminateArgs ps1 b2 + + let ps12 = let (a, b) = splitAt p1 ps1 in map strToPat (a ++ xs : b) -- Function arguments. + emptyLocalBinds = noLoc $ EmptyLocalBinds noExtField -- Empty where clause. + gRHS e = noLoc $ GRHS noExtField [] e :: LGRHS GhcPs (LHsExpr GhcPs) -- Guarded rhs. + gRHSSs e = GRHSs noExtField [gRHS e] emptyLocalBinds -- Guarded rhs set. + match e = Match{m_ext=noExtField,m_pats=ps12, m_grhss=gRHSSs e, ..} -- Match. matchGroup e = MG{mg_alts=noLoc [noLoc $ match e], mg_origin=Generated, ..} -- Match group. funBind e = FunBind {fun_matches=matchGroup e, ..} :: HsBindLR GhcPs GhcPs -- Fun bind. - return (ListCase ps b1 (x, xs, b2), noLoc . ValD noExt . funBind) + pure (ListCase ps b1 (x, xs, b2), noLoc . ValD noExtField . funBind) delCons :: String -> Int -> String -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs) -delCons func pos var (fromApps' -> (view' -> Var_' x) : xs) | func == x = do - (pre, (view' -> Var_' v) : post) <- return $ splitAt pos xs +delCons func pos var (fromApps -> (view -> Var_ x) : xs) | func == x = do + (pre, (view -> Var_ v) : post) <- pure $ splitAt pos xs guard $ v == var - return $ apps' $ recursive : pre ++ post -delCons _ _ _ x = return x + pure $ apps $ recursive : pre ++ post +delCons _ _ _ x = pure x eliminateArgs :: [String] -> LHsExpr GhcPs -> ([String], LHsExpr GhcPs) eliminateArgs ps cons = (remove ps, transform f cons) where - args = [zs | z : zs <- map fromApps' $ universeApps' cons, astEq z recursive] - elim = [all (\xs -> length xs > i && view' (xs !! i) == Var_' p) args | (i, p) <- zip [0..] ps] ++ repeat False + args = [zs | z : zs <- map fromApps $ universeApps cons, astEq z recursive] + elim = [all (\xs -> length xs > i && view (xs !! i) == Var_ p) args | (i, p) <- zipFrom 0 ps] ++ repeat False remove = concat . zipWith (\b x -> [x | not b]) elim - f (fromApps' -> x : xs) | astEq x recursive = apps' $ x : remove xs + f (fromApps -> x : xs) | astEq x recursive = apps $ x : remove xs f x = x @@ -207,22 +209,22 @@ GRHSs {grhssGRHSs=[L l (GRHS _ [] body)] , grhssLocalBinds=L _ (EmptyLocalBinds _) } - } <- return x + } <- pure x (a, b, c) <- findPat ps - return $ Branch (occNameString $rdrNameOcc name) a b c $ simplifyExp' body + pure $ Branch (occNameStr name) a b c $ simplifyExp body findPat :: [LPat GhcPs] -> Maybe ([String], Int, BList) findPat ps = do ps <- mapM readPat ps - [i] <- return $ findIndices isRight ps + [i] <- pure $ findIndices isRight ps let (left, [right]) = partitionEithers ps - return (left, i, right) + pure (left, i, right) -readPat :: Pat GhcPs -> Maybe (Either String BList) -readPat (view' -> PVar_' x) = Just $ Left x -readPat (LL _ (ParPat _ (LL _ (ConPatIn (L _ n) (InfixCon (view' -> PVar_' x) (view' -> PVar_' xs)))))) +readPat :: LPat GhcPs -> Maybe (Either String BList) +readPat (view -> PVar_ x) = Just $ Left x +readPat (L _ (ParPat _ (L _ (ConPatIn (L _ n) (InfixCon (view -> PVar_ x) (view -> PVar_ xs)))))) | n == consDataCon_RDR = Just $ Right $ BCons x xs -readPat (LL _ (ConPatIn (L _ n) (PrefixCon []))) +readPat (L _ (ConPatIn (L _ n) (PrefixCon []))) | n == nameRdrName nilDataConName = Just $ Right BNil readPat _ = Nothing diff -Nru hlint-2.2.11/src/Hint/Match.hs hlint-3.1.6/src/Hint/Match.hs --- hlint-2.2.11/src/Hint/Match.hs 2020-02-09 21:16:43.000000000 +0000 +++ hlint-3.1.6/src/Hint/Match.hs 2020-06-24 20:52:32.000000000 +0000 @@ -1,5 +1,5 @@ {-# LANGUAGE RecordWildCards, NamedFieldPuns, TupleSections #-} -{-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts, ScopedTypeVariables #-} +{-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts #-} {- The matching does a fairly simple unification between the two terms, treating @@ -8,7 +8,6 @@ both ($) and (.) functions on the right. TRANSFORM PATTERNS -_eval_ - perform deep evaluation, must be used at the top of a RHS _noParen_ - don't bracket this particular item SIDE CONDITIONS @@ -38,9 +37,9 @@ not . not . x ==> x -} -module Hint.Match(readMatch') where +module Hint.Match(readMatch) where -import Hint.Type (ModuleEx,Idea,idea',ideaNote,toSS') +import Hint.Type (ModuleEx,Idea,idea,ideaNote,toSS) import Util import Timing import qualified Data.Set as Set @@ -50,10 +49,10 @@ import Data.Tuple.Extra import Data.Maybe import Config.Type -import Data.Generics.Uniplate.Operations +import Data.Generics.Uniplate.DataOnly import Bag -import HsSyn +import GHC.Hs import SrcLoc import BasicTypes import RdrName @@ -62,85 +61,93 @@ import GHC.Util import Language.Haskell.GhclibParserEx.GHC.Hs.Expr import Language.Haskell.GhclibParserEx.GHC.Hs.ExtendInstances +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader -readMatch' :: [HintRule] -> Scope' -> ModuleEx -> LHsDecl GhcPs -> [Idea] -readMatch' settings = findIdeas' (concatMap readRule' settings) +readMatch :: [HintRule] -> Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea] +readMatch settings = findIdeas (concatMap readRule settings) -readRule' :: HintRule -> [HintRule] -readRule' m@HintRule{ hintRuleGhcLHS=(stripLocs' . unextendInstances -> hintRuleGhcLHS) - , hintRuleGhcRHS=(stripLocs' . unextendInstances -> hintRuleGhcRHS) - , hintRuleGhcSide=((stripLocs' . unextendInstances <$>) -> hintRuleGhcSide) +readRule :: HintRule -> [HintRule] +readRule m@HintRule{ hintRuleLHS=(stripLocs . unextendInstances -> hintRuleLHS) + , hintRuleRHS=(stripLocs . unextendInstances -> hintRuleRHS) + , hintRuleSide=((stripLocs . unextendInstances <$>) -> hintRuleSide) } = - (:) m{ hintRuleGhcLHS=extendInstances hintRuleGhcLHS - , hintRuleGhcRHS=extendInstances hintRuleGhcRHS - , hintRuleGhcSide=extendInstances <$> hintRuleGhcSide } $ do - (l, v1) <- dotVersion' hintRuleGhcLHS - (r, v2) <- dotVersion' hintRuleGhcRHS + (:) m{ hintRuleLHS=extendInstances hintRuleLHS + , hintRuleRHS=extendInstances hintRuleRHS + , hintRuleSide=extendInstances <$> hintRuleSide } $ do + (l, v1) <- dotVersion hintRuleLHS + (r, v2) <- dotVersion hintRuleRHS - guard $ v1 == v2 && not (null l) && (length l > 1 || length r > 1) && Set.notMember v1 (Set.map occNameString (freeVars' $ maybeToList hintRuleGhcSide ++ l ++ r)) + guard $ v1 == v2 && not (null l) && (length l > 1 || length r > 1) && Set.notMember v1 (Set.map occNameString (freeVars $ maybeToList hintRuleSide ++ l ++ r)) if not (null r) then - [ m{ hintRuleGhcLHS=extendInstances (dotApps' l), hintRuleGhcRHS=extendInstances (dotApps' r), hintRuleGhcSide=extendInstances <$> hintRuleGhcSide } - , m{ hintRuleGhcLHS=extendInstances (dotApps' (l ++ [strToVar v1])), hintRuleGhcRHS=extendInstances (dotApps' (r ++ [strToVar v1])), hintRuleGhcSide=extendInstances <$> hintRuleGhcSide } ] + [ m{ hintRuleLHS=extendInstances (dotApps l), hintRuleRHS=extendInstances (dotApps r), hintRuleSide=extendInstances <$> hintRuleSide } + , m{ hintRuleLHS=extendInstances (dotApps (l ++ [strToVar v1])), hintRuleRHS=extendInstances (dotApps (r ++ [strToVar v1])), hintRuleSide=extendInstances <$> hintRuleSide } ] else if length l > 1 then - [ m{ hintRuleGhcLHS=extendInstances (dotApps' l), hintRuleGhcRHS=extendInstances (strToVar "id"), hintRuleGhcSide=extendInstances <$> hintRuleGhcSide } - , m{ hintRuleGhcLHS=extendInstances (dotApps' (l++[strToVar v1])), hintRuleGhcRHS=extendInstances (strToVar v1), hintRuleGhcSide=extendInstances <$> hintRuleGhcSide}] + [ m{ hintRuleLHS=extendInstances (dotApps l), hintRuleRHS=extendInstances (strToVar "id"), hintRuleSide=extendInstances <$> hintRuleSide } + , m{ hintRuleLHS=extendInstances (dotApps (l++[strToVar v1])), hintRuleRHS=extendInstances (strToVar v1), hintRuleSide=extendInstances <$> hintRuleSide}] else [] -- Find a dot version of this rule, return the sequence of app -- prefixes, and the var. -dotVersion' :: LHsExpr GhcPs -> [([LHsExpr GhcPs], String)] -dotVersion' (view' -> Var_' v) | isUnifyVar v = [([], v)] -dotVersion' (LL _ (HsApp _ ls rs)) = first (ls :) <$> dotVersion' (fromParen' rs) -dotVersion' (LL l (OpApp _ x op y)) = +dotVersion :: LHsExpr GhcPs -> [([LHsExpr GhcPs], String)] +dotVersion (view -> Var_ v) | isUnifyVar v = [([], v)] +dotVersion (L _ (HsApp _ ls rs)) = first (ls :) <$> dotVersion (fromParen rs) +dotVersion (L l (OpApp _ x op y)) = -- In a GHC parse tree, raw sections aren't valid application terms. -- To be suitable as application terms, they must be enclosed in -- parentheses. -- If a == b then -- x is 'a', op is '==' and y is 'b' and, - let lSec = addParen' (cL l (SectionL noExt x op)) -- (a == ) - rSec = addParen' (cL l (SectionR noExt op y)) -- ( == b) - in (first (lSec :) <$> dotVersion' y) ++ (first (rSec :) <$> dotVersion' x) -- [([(a ==)], b), ([(b == )], a])]. -dotVersion' _ = [] + let lSec = addParen (cL l (SectionL noExtField x op)) -- (a == ) + rSec = addParen (cL l (SectionR noExtField op y)) -- ( == b) + in (first (lSec :) <$> dotVersion y) ++ (first (rSec :) <$> dotVersion x) -- [([(a ==)], b), ([(b == )], a])]. +dotVersion _ = [] --------------------------------------------------------------------- -- PERFORM THE MATCHING -findIdeas' :: [HintRule] -> Scope' -> ModuleEx -> LHsDecl GhcPs -> [Idea] -findIdeas' matches s _ decl = timed "Hint" "Match apply" $ forceList - [ (idea' (hintRuleSeverity m) (hintRuleName m) x y [r]){ideaNote=notes} - | (name, expr) <- findDecls' decl - , (parent,x) <- universeParentExp' expr - , m <- matches, Just (y, tpl, notes, subst) <- [matchIdea' s name m parent x] - , let r = R.Replace R.Expr (toSS' x) subst (unsafePrettyPrint tpl) +findIdeas :: [HintRule] -> Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea] +findIdeas matches s _ decl = timed "Hint" "Match apply" $ forceList + [ (idea (hintRuleSeverity m) (hintRuleName m) x y [r]){ideaNote=notes} + | (name, expr) <- findDecls decl + , (parent,x) <- universeParentExp expr + , m <- matches, Just (y, tpl, notes, subst) <- [matchIdea s name m parent x] + , let r = R.Replace R.Expr (toSS x) subst (unsafePrettyPrint tpl) ] -- | A list of root expressions, with their associated names -findDecls' :: LHsDecl GhcPs -> [(String, LHsExpr GhcPs)] -findDecls' x@(LL _ (InstD _ (ClsInstD _ ClsInstDecl{cid_binds}))) = +findDecls :: LHsDecl GhcPs -> [(String, LHsExpr GhcPs)] +findDecls x@(L _ (InstD _ (ClsInstD _ ClsInstDecl{cid_binds}))) = [(fromMaybe "" $ bindName xs, x) | xs <- bagToList cid_binds, x <- childrenBi xs] -findDecls' (LL _ RuleD{}) = [] -- Often rules contain things that HLint would rewrite. -findDecls' x = map (fromMaybe "" $ declName x,) $ childrenBi x +findDecls (L _ RuleD{}) = [] -- Often rules contain things that HLint would rewrite. +findDecls x = map (fromMaybe "" $ declName x,) $ childrenBi x -matchIdea' :: Scope' +matchIdea :: Scope -> String -> HintRule -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, LHsExpr GhcPs, [Note], [(String, R.SrcSpan)]) -matchIdea' sb declName HintRule{..} parent x = do - let lhs = unextendInstances hintRuleGhcLHS - rhs = unextendInstances hintRuleGhcRHS - sa = unextendInstances hintRuleGhcScope - nm a b = scopeMatch' (sa, a) (sb, b) - u <- unifyExp' nm True lhs x - u <- validSubst' astEq u +matchIdea sb declName HintRule{..} parent x = do + let lhs = unextendInstances hintRuleLHS + rhs = unextendInstances hintRuleRHS + sa = hintRuleScope + nm a b = scopeMatch (sa, a) (sb, b) + (u, extra) <- unifyExp nm True lhs x + u <- validSubst astEq u -- Need to check free vars before unqualification, but after subst -- (with 'e') need to unqualify before substitution (with 'res'). - let (e, tpl) = substitute' u rhs - res = addBracketTy' (addBracket' parent $ performSpecial' $ fst $ substitute' u $ unqualify' sa sb rhs) - guard $ (freeVars' e Set.\\ Set.filter (not . isUnifyVar . occNameString) (freeVars' rhs)) `Set.isSubsetOf` freeVars' x + let rhs' | Just fun <- extra = rebracket1 $ noLoc (HsApp noExtField fun rhs) + | otherwise = rhs + (e, tpl) = substitute u rhs' + noParens = [varToStr $ fromParen x | L _ (HsApp _ (varToStr -> "_noParen_") x) <- universe tpl] + + u <- pure (removeParens noParens u) + + let res = addBracketTy (addBracket parent $ performSpecial $ fst $ substitute u $ unqualify sa sb rhs') + guard $ (freeVars e Set.\\ Set.filter (not . isUnifyVar . occNameString) (freeVars rhs')) `Set.isSubsetOf` freeVars x -- Check no unexpected new free variables. -- Check it isn't going to get broken by QuasiQuotes as per #483. If @@ -149,117 +156,118 @@ -- what free vars they make use of. guard $ not (any isLambda $ universe lhs) || not (any isQuasiQuote $ universe x) - guard $ checkSide' (unextendInstances <$> hintRuleGhcSide) $ ("original", x) : ("result", res) : fromSubst' u - guard $ checkDefine' declName parent res + guard $ checkSide (unextendInstances <$> hintRuleSide) $ ("original", x) : ("result", res) : fromSubst u + guard $ checkDefine declName parent rhs + + (u, tpl) <- pure $ if any ((== noSrcSpan) . getLoc . snd) (fromSubst u) then (mempty, res) else (u, tpl) + tpl <- pure $ unqualify sa sb (performSpecial tpl) - return (res, tpl, hintRuleNotes, [(s, toSS' pos) | (s, pos) <- fromSubst' u, getLoc pos /= noSrcSpan]) + pure (res, tpl, hintRuleNotes, [(s, toSS pos) | (s, pos) <- fromSubst u, getLoc pos /= noSrcSpan]) --------------------------------------------------------------------- -- SIDE CONDITIONS -checkSide' :: Maybe (LHsExpr GhcPs) -> [(String, LHsExpr GhcPs)] -> Bool -checkSide' x bind = maybe True bool x +checkSide :: Maybe (LHsExpr GhcPs) -> [(String, LHsExpr GhcPs)] -> Bool +checkSide x bind = maybe True bool x where bool :: LHsExpr GhcPs -> Bool - bool (LL _ (OpApp _ x op y)) + bool (L _ (OpApp _ x op y)) | varToStr op == "&&" = bool x && bool y | varToStr op == "||" = bool x || bool y - | varToStr op == "==" = expr (fromParen1' x) `astEq` expr (fromParen1' y) - bool (LL _ (HsApp _ x y)) | varToStr x == "not" = not $ bool y - bool (LL _ (HsPar _ x)) = bool x + | varToStr op == "==" = expr (fromParen1 x) `astEq` expr (fromParen1 y) + bool (L _ (HsApp _ x y)) | varToStr x == "not" = not $ bool y + bool (L _ (HsPar _ x)) = bool x - bool (LL _ (HsApp _ cond (sub -> y))) + bool (L _ (HsApp _ cond (sub -> y))) | 'i' : 's' : typ <- varToStr cond = isType typ y - bool (LL _ (HsApp _ (LL _ (HsApp _ cond (sub -> x))) (sub -> y))) - | varToStr cond == "notIn" = and [extendInstances (stripLocs' x) `notElem` map (extendInstances . stripLocs') (universe y) | x <- list x, y <- list y] + bool (L _ (HsApp _ (L _ (HsApp _ cond (sub -> x))) (sub -> y))) + | varToStr cond == "notIn" = and [extendInstances (stripLocs x) `notElem` map (extendInstances . stripLocs) (universe y) | x <- list x, y <- list y] | varToStr cond == "notEq" = not (x `astEq` y) bool x | varToStr x == "noTypeCheck" = True bool x | varToStr x == "noQuickCheck" = True - bool x = error $ "Hint.Match.checkSide', unknown side condition: " ++ unsafePrettyPrint x + bool x = error $ "Hint.Match.checkSide, unknown side condition: " ++ unsafePrettyPrint x expr :: LHsExpr GhcPs -> LHsExpr GhcPs - expr (LL _ (HsApp _ (varToStr -> "subst") x)) = sub $ fromParen1' x + expr (L _ (HsApp _ (varToStr -> "subst") x)) = sub $ fromParen1 x expr x = x isType "Compare" x = True -- Just a hint for proof stuff - isType "Atom" x = isAtom' x + 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" (LL _ (HsLit _ HsInt{})) = True - isType "LitInt" (LL _ (HsOverLit _ (OverLit _ HsIntegral{} _))) = True - isType "Var" (LL _ HsVar{}) = True - isType "App" (LL _ HsApp{}) = True - isType "InfixApp" (LL _ x@OpApp{}) = True - isType "Paren" (LL _ x@HsPar{}) = True - isType "Tuple" (LL _ ExplicitTuple{}) = True + isType "LitInt" (L _ (HsLit _ HsInt{})) = True + isType "LitInt" (L _ (HsOverLit _ (OverLit _ HsIntegral{} _))) = True + isType "LitString" (L _ (HsLit _ HsString{})) = True + isType "Var" (L _ HsVar{}) = True + isType "App" (L _ HsApp{}) = True + isType "InfixApp" (L _ x@OpApp{}) = True + isType "Paren" (L _ x@HsPar{}) = True + isType "Tuple" (L _ ExplicitTuple{}) = True - isType typ (LL _ x) = + isType typ (L _ x) = let top = showConstr (toConstr x) in typ == top - isType _ _ = False -- {-# COMPLETE LL#-} asInt :: LHsExpr GhcPs -> Maybe Integer - asInt (LL _ (HsPar _ x)) = asInt x - asInt (LL _ (NegApp _ x _)) = negate <$> asInt x - asInt (LL _ (HsLit _ (HsInt _ (IL _ neg x)) )) = Just $ if neg then -x else x - asInt (LL _ (HsOverLit _ (OverLit _ (HsIntegral (IL _ neg x)) _))) = Just $ if neg then -x else x + asInt (L _ (HsPar _ x)) = asInt x + asInt (L _ (NegApp _ x _)) = negate <$> asInt x + asInt (L _ (HsLit _ (HsInt _ (IL _ neg x)) )) = Just $ if neg then -x else x + asInt (L _ (HsOverLit _ (OverLit _ (HsIntegral (IL _ neg x)) _))) = Just $ if neg then -x else x asInt _ = Nothing list :: LHsExpr GhcPs -> [LHsExpr GhcPs] - list (LL _ (ExplicitList _ _ xs)) = xs + list (L _ (ExplicitList _ _ xs)) = xs list x = [x] sub :: LHsExpr GhcPs -> LHsExpr GhcPs sub = transform f - where f (view' -> Var_' x) | Just y <- lookup x bind = y + where f (view -> Var_ x) | Just y <- lookup x bind = y f x = x -- Does the result look very much like the declaration? -checkDefine' :: String -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool -checkDefine' declName Nothing y = +checkDefine :: String -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool +checkDefine declName Nothing y = let funOrOp expr = case expr of - LL _ (HsApp _ fun _) -> funOrOp fun - LL _ (OpApp _ _ op _) -> funOrOp op + L _ (HsApp _ fun _) -> funOrOp fun + L _ (OpApp _ _ op _) -> funOrOp op other -> other - in declName /= varToStr (transformBi unqual' $ funOrOp y) -checkDefine' _ _ _ = True + in declName /= varToStr (transformBi unqual $ funOrOp y) +checkDefine _ _ _ = True --------------------------------------------------------------------- -- TRANSFORMATION --- If it has '_eval_' do evaluation on it. -performSpecial' :: LHsExpr GhcPs -> LHsExpr GhcPs -performSpecial' = transform fNoParen . fEval +-- If it has '_noParen_', remove the brackets (if exist). +performSpecial :: LHsExpr GhcPs -> LHsExpr GhcPs +performSpecial = transform fNoParen where - fEval, fNoParen :: LHsExpr GhcPs -> LHsExpr GhcPs - fEval (LL _ (HsApp _ e x)) | varToStr e == "_eval_" = reduce' x - fEval x = x - fNoParen (LL _ (HsApp _ e x)) | varToStr e == "_noParen_" = fromParen' x + fNoParen :: LHsExpr GhcPs -> LHsExpr GhcPs + fNoParen (L _ (HsApp _ e x)) | varToStr e == "_noParen_" = fromParen x fNoParen x = x -- Contract : 'Data.List.foo' => 'foo' if 'Data.List' is loaded. -unqualify' :: Scope' -> Scope' -> LHsExpr GhcPs -> LHsExpr GhcPs -unqualify' from to = transformBi f +unqualify :: Scope -> Scope -> LHsExpr GhcPs -> LHsExpr GhcPs +unqualify from to = transformBi f where f :: Located RdrName -> Located RdrName f x@(L _ (Unqual s)) | isUnifyVar (occNameString s) = x - f x = scopeMove' (from, x) to + f x = scopeMove (from, x) to -addBracket' :: Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> LHsExpr GhcPs -addBracket' (Just (i, p)) c | needBracketOld' i p c = noLoc $ HsPar noExt c -addBracket' _ x = x +addBracket :: Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> LHsExpr GhcPs +addBracket (Just (i, p)) c | needBracketOld i p c = noLoc $ HsPar noExtField c +addBracket _ x = x -- Type substitution e.g. 'Foo Int' for 'a' in 'Proxy a' can lead to a -- need to bracket type applications in This doesn't come up in HSE -- because the pretty printer inserts them. -addBracketTy' :: LHsExpr GhcPs -> LHsExpr GhcPs -addBracketTy'= transformBi f +addBracketTy :: LHsExpr GhcPs -> LHsExpr GhcPs +addBracketTy= transformBi f where f :: LHsType GhcPs -> LHsType GhcPs - f (LL _ (HsAppTy _ t x@(LL _ HsAppTy{}))) = - noLoc (HsAppTy noExt t (noLoc (HsParTy noExt x))) + f (L _ (HsAppTy _ t x@(L _ HsAppTy{}))) = + noLoc (HsAppTy noExtField t (noLoc (HsParTy noExtField x))) f x = x diff -Nru hlint-2.2.11/src/Hint/Monad.hs hlint-3.1.6/src/Hint/Monad.hs --- hlint-2.2.11/src/Hint/Monad.hs 2020-02-07 10:36:33.000000000 +0000 +++ hlint-3.1.6/src/Hint/Monad.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,4 +1,4 @@ -{-# LANGUAGE ViewPatterns, PatternGuards, FlexibleContexts #-} +{-# LANGUAGE LambdaCase, ViewPatterns, PatternGuards, FlexibleContexts #-} {- Find and match: @@ -13,7 +13,7 @@ yes = do _ <- mapM print a; return b -- mapM_ print a no = mapM print a no = do foo ; mapM print a -yes = do (bar+foo) -- (bar+foo) +yes = do (bar+foo) -- no = do bar ; foo yes = do bar; a <- foo; return a -- do bar; foo no = do bar; a <- foo; return b @@ -27,8 +27,8 @@ yes = do x <- bar $ baz; return (f $ g x) no = do x <- bar; return (f x x) {-# LANGUAGE RecursiveDo #-}; no = mdo hook <- mkTrigger pat (act >> rmHook hook) ; return hook -yes = do x <- return y; foo x -- @Suggestion do let x = y; foo x -yes = do x <- return $ y + z; foo x -- do let x = y + z; foo x +yes = do x <- return y; foo x -- @Suggestion let x = y +yes = do x <- return $ y + z; foo x -- let x = y + z no = do x <- return x; foo x no = do x <- return y; x <- return y; foo x yes = do forM files $ \x -> return (); return () -- forM_ files $ \x -> return () @@ -39,93 +39,140 @@ folder f a xs = foldM f a xs >>= \_ -> return () -- foldM_ f a xs yes = mapM async ds >>= mapM wait >> return () -- mapM async ds >>= mapM_ wait main = "wait" ~> do f a $ sleep 10 -main = print do 17 + 25 -main = print do 17 -- 17 -main = f $ do g a $ sleep 10 -- g a $ sleep 10 -main = do f a $ sleep 10 -- f a $ sleep 10 +{-# LANGUAGE BlockArguments #-}; main = print do 17 + 25 +{-# LANGUAGE BlockArguments #-}; main = print do 17 -- +main = f $ do g a $ sleep 10 -- +main = do f a $ sleep 10 -- @Ignore main = do foo x; return 3; bar z -- do foo x; bar z main = void $ forM_ f xs -- forM_ f xs main = void $ forM f xs -- void $ forM_ f xs main = do _ <- forM_ f xs; bar -- forM_ f xs main = do bar; forM_ f xs; return () -- do bar; forM_ f xs main = do a; when b c; return () -- do a; when b c +bar = 1 * do {\x -> x+x} + y +issue978 = do \ + print "x" \ + if False then main else do \ + return () -} module Hint.Monad(monadHint) where -import Hint.Type(DeclHint',Idea,ideaNote,warn',toSS',suggest',Note(Note)) +import Hint.Type(DeclHint,Idea(..),Severity(..),ideaNote,warn,ideaRemove,toSS,suggest,Note(Note)) -import HsSyn +import GHC.Hs import SrcLoc import BasicTypes import TcEvidence import RdrName import OccName import Bag +import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader import GHC.Util +import Data.Generics.Uniplate.DataOnly import Data.Tuple.Extra import Data.Maybe import Data.List.Extra import Refact.Types hiding (Match) import qualified Refact.Types as R + badFuncs :: [String] badFuncs = ["mapM","foldM","forM","replicateM","sequence","zipWithM","traverse","for","sequenceA"] unitFuncs :: [String] unitFuncs = ["when","unless","void"] -monadHint :: DeclHint' -monadHint _ _ d = concatMap (monadExp d) $ universeParentExp' d - -monadExp :: LHsDecl GhcPs -> (Maybe (Int, LHsExpr GhcPs), LHsExpr GhcPs) -> [Idea] -monadExp (declName -> decl) (parent, x) = +monadHint :: DeclHint +monadHint _ _ d = concatMap (f Nothing Nothing) $ childrenBi d + where + decl = declName d + f parentDo parentExpr x = + monadExp decl parentDo parentExpr x ++ + concat [f (if isHsDo x then Just x else parentDo) (Just (i, x)) c | (i, c) <- zipFrom 0 $ children x] + + isHsDo (L _ HsDo{}) = True + isHsDo _ = False + + +-- | Call with the name of the declaration, +-- the nearest enclosing `do` expression +-- the nearest enclosing expression +-- the expression of interest +monadExp :: Maybe String -> Maybe (LHsExpr GhcPs) -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> [Idea] +monadExp decl parentDo parentExpr x = case x of - (view' -> App2' op x1 x2) | isTag ">>" op -> f x1 - (view' -> App2' op x1 (view' -> LamConst1' _)) | isTag ">>=" op -> f x1 - (LL l (HsApp _ op x)) | isTag "void" op -> seenVoid (cL l . HsApp noExt op) x - (LL l (OpApp _ op dol x)) | isTag "void" op, isDol dol -> seenVoid (cL l . OpApp noExt op dol) x - (LL loc (HsDo _ _ (LL _ [LL _ (BodyStmt _ y _ _ )]))) -> [warn' "Redundant do" x y [Replace Expr (toSS' x) [("y", toSS' y)] "y"] | not $ doOperator parent y] - (LL loc (HsDo _ DoExpr (L _ xs))) -> - monadSteps (cL loc . HsDo noExt DoExpr . noLoc) xs ++ - [suggest' "Use let" x (cL loc (HsDo noExt DoExpr (noLoc y)) :: LHsExpr GhcPs) rs | Just (y, rs) <- [monadLet xs]] ++ - concat [f x | (LL _ (BodyStmt _ x _ _)) <- init xs] ++ - concat [f x | (LL _ (BindStmt _ (LL _ WildPat{}) x _ _)) <- init xs] + (view -> App2 op x1 x2) | isTag ">>" op -> f x1 + (view -> App2 op x1 (view -> LamConst1 _)) | isTag ">>=" op -> f x1 + (L l (HsApp _ op x)) | isTag "void" op -> seenVoid (cL l . HsApp noExtField op) x + (L l (OpApp _ op dol x)) | isTag "void" op, isDol dol -> seenVoid (cL l . OpApp noExtField op dol) x + (L loc (HsDo _ ctx (L loc2 [L loc3 (BodyStmt _ y _ _ )]))) -> + let doOrMDo = case ctx of MDoExpr -> "mdo"; _ -> "do" + in [ ideaRemove Ignore ("Redundant " ++ doOrMDo) (doSpan doOrMDo loc) doOrMDo [Replace Expr (toSS x) [("y", toSS y)] "y"] + | not $ doAsBrackets parentExpr y + , not $ doAsAvoidingIndentation parentDo x + ] + (L loc (HsDo _ DoExpr (L _ xs))) -> + monadSteps (cL loc . HsDo noExtField DoExpr . noLoc) xs ++ + [suggest "Use let" from to [r] | (from, to, r) <- monadLet xs] ++ + concat [f x | (L _ (BodyStmt _ x _ _)) <- dropEnd1 xs] ++ + concat [f x | (L _ (BindStmt _ (LL _ WildPat{}) x _ _)) <- dropEnd1 xs] _ -> [] where f = monadNoResult (fromMaybe "" decl) id - seenVoid wrap x = monadNoResult (fromMaybe "" decl) wrap x ++ [warn' "Redundant void" (wrap x) x [] | returnsUnit x] + seenVoid wrap x = monadNoResult (fromMaybe "" decl) wrap x ++ [warn "Redundant void" (wrap x) x [] | returnsUnit x] + doSpan doOrMDo = \case + UnhelpfulSpan s -> UnhelpfulSpan s + RealSrcSpan s -> + let start = realSrcSpanStart s + end = mkRealSrcLoc (srcSpanFile s) (srcLocLine start) (srcLocCol start + length doOrMDo) + in RealSrcSpan (mkRealSrcSpan start end) + +-- Sometimes people write 'a * do a + b', to avoid brackets, +-- or using BlockArguments they can write 'a do a b', +-- or using indentation a * do {\b -> c} * d +-- Return True if they are using do as brackets +doAsBrackets :: Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool +doAsBrackets (Just (2, L _ (OpApp _ _ op _ ))) _ | isDol op = False -- not quite atomic, but close enough +doAsBrackets (Just (i, o)) x = needBracket i o x +doAsBrackets Nothing x = False + + +-- Sometimes people write do, to avoid identation, see +-- https://github.com/ndmitchell/hlint/issues/978 +-- Return True if they are using do as avoiding identation +doAsAvoidingIndentation :: Maybe (LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool +doAsAvoidingIndentation (Just (L _ (HsDo _ _ (L (RealSrcSpan a) _)))) (L _ (HsDo _ _ (L (RealSrcSpan b) _))) + = srcSpanStartCol a == srcSpanStartCol b +doAsAvoidingIndentation parent self = False --- Sometimes people write 'a * do a + b', to avoid brackets. --- or using BlockArguments they can write 'a do a b' -doOperator :: (Eq a, Num a) => Maybe (a, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool -doOperator (Just (2, LL _ (OpApp _ _ op _ ))) (LL _ OpApp {}) | not $ isDol op = True -doOperator (Just (1, LL _ HsApp{})) b | not $ isAtom' b = True -doOperator _ _ = False returnsUnit :: LHsExpr GhcPs -> Bool -returnsUnit (LL _ (HsPar _ x)) = returnsUnit x -returnsUnit (LL _ (HsApp _ x _)) = returnsUnit x -returnsUnit (LL _ (OpApp _ x op _)) | isDol op = returnsUnit x -returnsUnit (LL _ (HsVar _ (L _ x))) = occNameString (rdrNameOcc x) `elem` map (++ "_") badFuncs ++ unitFuncs +returnsUnit (L _ (HsPar _ x)) = returnsUnit x +returnsUnit (L _ (HsApp _ x _)) = returnsUnit x +returnsUnit (L _ (OpApp _ x op _)) | isDol op = returnsUnit x +returnsUnit (L _ (HsVar _ (L _ x))) = occNameStr x `elem` map (++ "_") badFuncs ++ unitFuncs returnsUnit _ = False -- See through HsPar, and down HsIf/HsCase, return the name to use in -- the hint, and the revised expression. monadNoResult :: String -> (LHsExpr GhcPs -> LHsExpr GhcPs) -> LHsExpr GhcPs -> [Idea] -monadNoResult inside wrap (LL l (HsPar _ x)) = monadNoResult inside (wrap . cL l . HsPar noExt) x -monadNoResult inside wrap (LL l (HsApp _ x y)) = monadNoResult inside (\x -> wrap $ cL l (HsApp noExt x y)) x -monadNoResult inside wrap (LL l (OpApp _ x tag@(LL _ (HsVar _ (L _ op))) y)) - | isDol tag = monadNoResult inside (\x -> wrap $ cL l (OpApp noExt x tag y)) x - | occNameString (rdrNameOcc op) == ">>=" = monadNoResult inside (wrap . cL l . OpApp noExt x tag) y +monadNoResult inside wrap (L l (HsPar _ x)) = monadNoResult inside (wrap . cL l . HsPar noExtField) x +monadNoResult inside wrap (L l (HsApp _ x y)) = monadNoResult inside (\x -> wrap $ cL l (HsApp noExtField x y)) x +monadNoResult inside wrap (L l (OpApp _ x tag@(L _ (HsVar _ (L _ op))) y)) + | isDol tag = monadNoResult inside (\x -> wrap $ cL l (OpApp noExtField x tag y)) x + | occNameStr op == ">>=" = monadNoResult inside (wrap . cL l . OpApp noExtField x tag) y monadNoResult inside wrap x | x2 : _ <- filter (`isTag` x) badFuncs , let x3 = x2 ++ "_" - = [warn' ("Use " ++ x3) (wrap x) (wrap $ strToVar x3) [Replace Expr (toSS' x) [] x3] | inside /= x3] -monadNoResult inside wrap (replaceBranches' -> (bs, rewrap)) = + + = [warn ("Use " ++ x3) (wrap x) (wrap $ strToVar x3) [Replace Expr (toSS x) [] x3] | inside /= x3] +monadNoResult inside wrap (replaceBranches -> (bs, rewrap)) = map (\x -> x{ideaNote=nubOrd $ Note "May require adding void to other branches" : ideaNote x}) $ concat [monadNoResult inside id b | b <- bs] @@ -133,53 +180,53 @@ -> [ExprLStmt GhcPs] -> [Idea] -- Rewrite 'do return x; $2' as 'do $2'. -monadStep wrap os@(o@(LL _ (BodyStmt _ (fromRet -> Just (ret, _)) _ _ )) : xs@(_:_)) - = [warn' ("Redundant " ++ ret) (wrap os) (wrap xs) [Delete Stmt (toSS' o)]] +monadStep wrap os@(o@(L _ (BodyStmt _ (fromRet -> Just (ret, _)) _ _ )) : xs@(_:_)) + = [warn ("Redundant " ++ ret) (wrap os) (wrap xs) [Delete Stmt (toSS o)]] -- Rewrite 'do a <- $1; return a' as 'do $1'. -monadStep wrap o@[ g@(LL _ (BindStmt _ (LL _ (VarPat _ (L _ p))) x _ _ )) - , q@(LL _ (BodyStmt _ (fromRet -> Just (ret, LL _ (HsVar _ (L _ v)))) _ _))] - | occNameString (rdrNameOcc p) == occNameString (rdrNameOcc v) - = [warn' ("Redundant " ++ ret) (wrap o) (wrap [noLoc $ BodyStmt noExt x noSyntaxExpr noSyntaxExpr]) - [Replace Stmt (toSS' g) [("x", toSS' x)] "x", Delete Stmt (toSS' q)]] +monadStep wrap o@[ g@(L _ (BindStmt _ (LL _ (VarPat _ (L _ p))) x _ _ )) + , q@(L _ (BodyStmt _ (fromRet -> Just (ret, L _ (HsVar _ (L _ v)))) _ _))] + | occNameStr p == occNameStr v + = [warn ("Redundant " ++ ret) (wrap o) (wrap [noLoc $ BodyStmt noExtField x noSyntaxExpr noSyntaxExpr]) + [Replace Stmt (toSS g) [("x", toSS x)] "x", Delete Stmt (toSS q)]] -- Suggest to use join. Rewrite 'do x <- $1; x; $2' as 'do join $1; $2'. -monadStep wrap o@(g@(LL _ (BindStmt _ (view' -> PVar_' p) x _ _)):q@(LL _ (BodyStmt _ (view' -> Var_' v) _ _)):xs) - | p == v && v `notElem` varss' xs - = let app = noLoc $ HsApp noExt (strToVar "join") x - body = noLoc $ BodyStmt noExt (rebracket1' app) noSyntaxExpr noSyntaxExpr +monadStep wrap o@(g@(L _ (BindStmt _ (view -> PVar_ p) x _ _)):q@(L _ (BodyStmt _ (view -> Var_ v) _ _)):xs) + | p == v && v `notElem` varss xs + = let app = noLoc $ HsApp noExtField (strToVar "join") x + body = noLoc $ BodyStmt noExtField (rebracket1 app) noSyntaxExpr noSyntaxExpr stmts = body : xs - in [warn' "Use join" (wrap o) (wrap stmts) r] - where r = [Replace Stmt (toSS' g) [("x", toSS' x)] "join x", Delete Stmt (toSS' q)] + in [warn "Use join" (wrap o) (wrap stmts) r] + where r = [Replace Stmt (toSS g) [("x", toSS x)] "join x", Delete Stmt (toSS q)] -- Redundant variable capture. Rewrite 'do _ <- ; $1' as -- 'do ; $1'. -monadStep wrap (o@(LL loc (BindStmt _ p x _ _)) : rest) - | isPWildCard' p, returnsUnit x - = let body = cL loc $ BodyStmt noExt x noSyntaxExpr noSyntaxExpr :: ExprLStmt GhcPs - in [warn' "Redundant variable capture" o body []] +monadStep wrap (o@(L loc (BindStmt _ p x _ _)) : rest) + | isPWildcard p, returnsUnit x + = let body = cL loc $ BodyStmt noExtField x noSyntaxExpr noSyntaxExpr :: ExprLStmt GhcPs + in [warn "Redundant variable capture" o body []] -- Redundant unit return : 'do ; return ()'. monadStep - wrap o@[ LL _ (BodyStmt _ x _ _) - , LL _ (BodyStmt _ (fromRet -> Just (ret, LL _ (HsVar _ (L _ unit)))) _ _)] - | returnsUnit x, occNameString (rdrNameOcc unit) == "()" - = [warn' ("Redundant " ++ ret) (wrap o) (wrap $ take 1 o) []] + wrap o@[ L _ (BodyStmt _ x _ _) + , L _ (BodyStmt _ (fromRet -> Just (ret, L _ (HsVar _ (L _ unit)))) _ _)] + | returnsUnit x, occNameStr unit == "()" + = [warn ("Redundant " ++ ret) (wrap o) (wrap $ take 1 o) []] -- Rewrite 'do x <- $1; return $ f $ g x' as 'f . g <$> x' monadStep wrap - o@[g@(LL _ (BindStmt _ (view' -> PVar_' u) x _ _)) - , q@(LL _ (BodyStmt _ (fromApplies -> (ret:f:fs, view' -> Var_' v)) _ _))] - | isReturn ret, notDol x, u == v, length fs < 3, all isSimple (f : fs), v `notElem` vars' (f : fs) + o@[g@(L _ (BindStmt _ (view -> PVar_ u) x _ _)) + , q@(L _ (BodyStmt _ (fromApplies -> (ret:f:fs, view -> Var_ v)) _ _))] + | isReturn ret, notDol x, u == v, length fs < 3, all isSimple (f : fs), v `notElem` vars (f : fs) = - [warn' "Use <$>" (wrap o) (wrap [noLoc $ BodyStmt noExt (noLoc $ OpApp noExt (foldl' (\acc e -> noLoc $ OpApp noExt acc (strToVar ".") e) f fs) (strToVar "<$>") x) noSyntaxExpr noSyntaxExpr]) - [Replace Stmt (toSS' g) (("x", toSS' x):zip vs (toSS' <$> f:fs)) (intercalate " . " (take (length fs + 1) vs) ++ " <$> x"), Delete Stmt (toSS' q)]] + [warn "Use <$>" (wrap o) (wrap [noLoc $ BodyStmt noExtField (noLoc $ OpApp noExtField (foldl' (\acc e -> noLoc $ OpApp noExtField acc (strToVar ".") e) f fs) (strToVar "<$>") x) noSyntaxExpr noSyntaxExpr]) + [Replace Stmt (toSS g) (("x", toSS x):zip vs (toSS <$> f:fs)) (intercalate " . " (take (length fs + 1) vs) ++ " <$> x"), Delete Stmt (toSS q)]] where - isSimple (fromApps' -> xs) = all isAtom' (x : xs) + isSimple (fromApps -> xs) = all isAtom (x : xs) vs = ('f':) . show <$> [0..] notDol :: LHsExpr GhcPs -> Bool - notDol (LL _ (OpApp _ _ op _)) = not $ isDol op + notDol (L _ (OpApp _ _ op _)) = not $ isDol op notDol _ = True monadStep _ _ = [] @@ -190,40 +237,39 @@ monadSteps _ _ = [] -- | Rewrite 'do ...; x <- return y; ...' as 'do ...; let x = y; ...'. -monadLet :: [ExprLStmt GhcPs] -> Maybe ([ExprLStmt GhcPs], [Refactoring R.SrcSpan]) -monadLet xs = if null rs then Nothing else Just (ys, rs) +monadLet :: [ExprLStmt GhcPs] -> [(ExprLStmt GhcPs, ExprLStmt GhcPs, Refactoring R.SrcSpan)] +monadLet xs = mapMaybe mkLet xs where - (ys, catMaybes -> rs) = unzip $ map mkLet xs - vs = concatMap pvars' [p | (LL _ (BindStmt _ p _ _ _)) <- xs] + vs = concatMap pvars [p | (L _ (BindStmt _ p _ _ _)) <- xs] - mkLet :: ExprLStmt GhcPs -> (ExprLStmt GhcPs, Maybe (Refactoring R.SrcSpan)) - mkLet g@(LL _ (BindStmt _ v@(view' -> PVar_' p) (fromRet -> Just (_, y)) _ _ )) - | p `notElem` vars' y, p `notElem` delete p vs - = (template p y, Just refact) + mkLet :: ExprLStmt GhcPs -> Maybe (ExprLStmt GhcPs, ExprLStmt GhcPs, Refactoring R.SrcSpan) + mkLet x@(L _ (BindStmt _ v@(view -> PVar_ p) (fromRet -> Just (_, y)) _ _ )) + | p `notElem` vars y, p `notElem` delete p vs + = Just (x, template p y, refact) where - refact = Replace Stmt (toSS' g) [("lhs", toSS' v), ("rhs", toSS' y)] + refact = Replace Stmt (toSS x) [("lhs", toSS v), ("rhs", toSS y)] (unsafePrettyPrint $ template "lhs" (strToVar "rhs")) - mkLet x = (x, Nothing) + mkLet _ = Nothing template :: String -> LHsExpr GhcPs -> ExprLStmt GhcPs template lhs rhs = let p = noLoc $ mkRdrUnqual (mkVarOcc lhs) - grhs = noLoc (GRHS noExt [] rhs) - grhss = GRHSs noExt [grhs] (noLoc (EmptyLocalBinds noExt)) - match = noLoc $ Match noExt (FunRhs p Prefix NoSrcStrict) [] grhss - fb = noLoc $ FunBind noExt p (MG noExt (noLoc [match]) Generated) WpHole [] + grhs = noLoc (GRHS noExtField [] rhs) + grhss = GRHSs noExtField [grhs] (noLoc (EmptyLocalBinds noExtField)) + match = noLoc $ Match noExtField (FunRhs p Prefix NoSrcStrict) [] grhss + fb = noLoc $ FunBind noExtField p (MG noExtField (noLoc [match]) Generated) WpHole [] binds = unitBag fb - valBinds = ValBinds noExt binds [] - localBinds = noLoc $ HsValBinds noExt valBinds - in noLoc $ LetStmt noExt localBinds + valBinds = ValBinds noExtField binds [] + localBinds = noLoc $ HsValBinds noExtField valBinds + in noLoc $ LetStmt noExtField localBinds fromApplies :: LHsExpr GhcPs -> ([LHsExpr GhcPs], LHsExpr GhcPs) -fromApplies (LL _ (HsApp _ f x)) = first (f:) $ fromApplies (fromParen' x) -fromApplies (LL _ (OpApp _ f (isDol -> True) x)) = first (f:) $ fromApplies x +fromApplies (L _ (HsApp _ f x)) = first (f:) $ fromApplies (fromParen x) +fromApplies (L _ (OpApp _ f (isDol -> True) x)) = first (f:) $ fromApplies x fromApplies x = ([], x) fromRet :: LHsExpr GhcPs -> Maybe (String, LHsExpr GhcPs) -fromRet (LL _ (HsPar _ x)) = fromRet x -fromRet (LL _ (OpApp _ x (LL _ (HsVar _ (L _ y))) z)) | occNameString (rdrNameOcc y) == "$" = fromRet $ noLoc (HsApp noExt x z) -fromRet (LL _ (HsApp _ x y)) | isReturn x = Just (unsafePrettyPrint x, y) +fromRet (L _ (HsPar _ x)) = fromRet x +fromRet (L _ (OpApp _ x (L _ (HsVar _ (L _ y))) z)) | occNameStr y == "$" = fromRet $ noLoc (HsApp noExtField x z) +fromRet (L _ (HsApp _ x y)) | isReturn x = Just (unsafePrettyPrint x, y) fromRet _ = Nothing diff -Nru hlint-2.2.11/src/Hint/Naming.hs hlint-3.1.6/src/Hint/Naming.hs --- hlint-2.2.11/src/Hint/Naming.hs 2019-11-02 16:50:13.000000000 +0000 +++ hlint-3.1.6/src/Hint/Naming.hs 2020-06-14 18:45:05.000000000 +0000 @@ -15,25 +15,25 @@ Don't suggest for FFI, since they match their C names -data Yes = Foo | Bar'Test -- data Yes = Foo | BarTest -data Yes = Bar | Test_Bar -- data Yes = Bar | TestBar +data Yes = Foo | Bar'Test +data Yes = Bar | Test_Bar -- data Yes = Bar | TestBar @NoRefactor data No = a :::: b data Yes = Foo {bar_cap :: Int} data No = FOO | BarBAR | BarBBar -yes_foo = yes_foo + yes_foo -- yesFoo = ... -yes_fooPattern Nothing = 0 -- yesFooPattern Nothing = ... +yes_foo = yes_foo + yes_foo -- yesFoo = ... @NoRefactor +yes_fooPattern Nothing = 0 -- yesFooPattern Nothing = ... @NoRefactor no = 1 where yes_foo = 2 a -== b = 1 myTest = 1; my_test = 1 -semiring'laws = 1 -- semiringLaws = ... -data Yes = FOO_A | Foo_B -- data Yes = FOO_A | FooB +semiring'laws = 1 +data Yes = FOO_A | Foo_B -- data Yes = FOO_A | FooB @NoRefactor case_foo = 1 test_foo = 1 -cast_foo = 1 -- castFoo = ... +cast_foo = 1 -- castFoo = ... @NoRefactor replicateM_ = 1 _foo__ = 1 section_1_1 = 1 -runMutator# = 1 +runMutator# = 1 @NoRefactor foreign import ccall hexml_node_child :: IO () -} @@ -41,40 +41,42 @@ module Hint.Naming(namingHint) where -import Hint.Type (Idea,DeclHint',suggest',isSym,toSrcSpan',ghcModule) -import Data.Generics.Uniplate.Operations +import Hint.Type (Idea,DeclHint,suggest,ghcModule) +import Data.Generics.Uniplate.DataOnly import Data.List.Extra (nubOrd, isPrefixOf) import Data.Data import Data.Char import Data.Maybe -import Refact.Types hiding (RType(Match)) import qualified Data.Set as Set import BasicTypes import FastString -import HsDecls -import HsExtension -import HsSyn +import GHC.Hs.Decls +import GHC.Hs.Extension +import GHC.Hs import OccName import SrcLoc +import Language.Haskell.GhclibParserEx.GHC.Hs.Decls +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable import GHC.Util -namingHint :: DeclHint' +namingHint :: DeclHint namingHint _ modu = naming $ Set.fromList $ concatMap getNames $ hsmodDecls $ unLoc (ghcModule modu) naming :: Set.Set String -> LHsDecl GhcPs -> [Idea] naming seen originalDecl = - [ suggest' "Use camelCase" + [ suggest "Use camelCase" (shorten originalDecl) (shorten replacedDecl) - [Replace Bind (toSrcSpan' originalDecl) [] (unsafePrettyPrint replacedDecl)] + [ -- https://github.com/mpickering/apply-refact/issues/39 + ] | not $ null suggestedNames ] where suggestedNames = [ (originalName, suggestedName) - | not $ isForD' originalDecl + | not $ isForD originalDecl , originalName <- nubOrd $ getNames originalDecl , Just suggestedName <- [suggestName originalName] , not $ suggestedName `Set.member` seen @@ -82,23 +84,23 @@ replacedDecl = replaceNames suggestedNames originalDecl shorten :: LHsDecl GhcPs -> LHsDecl GhcPs -shorten (LL locDecl (ValD ttg0 bind@(FunBind _ _ matchGroup@(MG _ (LL locMatches matches) FromSource) _ _))) = - LL locDecl (ValD ttg0 bind {fun_matches = matchGroup {mg_alts = LL locMatches $ map shortenMatch matches}}) -shorten (LL locDecl (ValD ttg0 bind@(PatBind _ _ grhss@(GRHSs _ rhss _) _))) = - LL locDecl (ValD ttg0 bind {pat_rhs = grhss {grhssGRHSs = map shortenLGRHS rhss}}) +shorten (L locDecl (ValD ttg0 bind@(FunBind _ _ matchGroup@(MG _ (L locMatches matches) FromSource) _ _))) = + L locDecl (ValD ttg0 bind {fun_matches = matchGroup {mg_alts = L locMatches $ map shortenMatch matches}}) +shorten (L locDecl (ValD ttg0 bind@(PatBind _ _ grhss@(GRHSs _ rhss _) _))) = + L locDecl (ValD ttg0 bind {pat_rhs = grhss {grhssGRHSs = map shortenLGRHS rhss}}) shorten x = x shortenMatch :: LMatch GhcPs (LHsExpr GhcPs) -> LMatch GhcPs (LHsExpr GhcPs) -shortenMatch (LL locMatch match@(Match _ _ _ grhss@(GRHSs _ rhss _))) = - LL locMatch match {m_grhss = grhss {grhssGRHSs = map shortenLGRHS rhss}} +shortenMatch (L locMatch match@(Match _ _ _ grhss@(GRHSs _ rhss _))) = + L locMatch match {m_grhss = grhss {grhssGRHSs = map shortenLGRHS rhss}} shortenMatch x = x shortenLGRHS :: LGRHS GhcPs (LHsExpr GhcPs) -> LGRHS GhcPs (LHsExpr GhcPs) -shortenLGRHS (LL locGRHS (GRHS ttg0 guards (LL locExpr _))) = - LL locGRHS (GRHS ttg0 guards (cL locExpr dots)) +shortenLGRHS (L locGRHS (GRHS ttg0 guards (L locExpr _))) = + L locGRHS (GRHS ttg0 guards (cL locExpr dots)) where dots :: HsExpr GhcPs - dots = HsLit NoExt (HsString (SourceText "...") (mkFastString "...")) + dots = HsLit noExtField (HsString (SourceText "...") (mkFastString "...")) shortenLGRHS x = x getNames :: LHsDecl GhcPs -> [String] @@ -109,13 +111,17 @@ concatMap (map unsafePrettyPrint . getConNames . unLoc) cons getConstructorNames _ = [] +isSym :: String -> Bool +isSym (x:_) = not $ isAlpha x || x `elem` "_'" +isSym _ = False + suggestName :: String -> Maybe String suggestName original | isSym original || good || not (any isLower original) || any isDigit original || any (`isPrefixOf` original) ["prop_","case_","unit_","test_","spec_","scprop_","hprop_"] = Nothing | otherwise = Just $ f original where - good = all isAlphaNum $ drp '_' $ drp '#' $ drp '\'' $ reverse $ drp '_' original + good = all isAlphaNum $ drp '_' $ drp '#' $ filter (/= '\'') $ reverse $ drp '_' original drp x = dropWhile (== x) f xs = us ++ g ys diff -Nru hlint-2.2.11/src/Hint/NewType.hs hlint-3.1.6/src/Hint/NewType.hs --- hlint-2.2.11/src/Hint/NewType.hs 2019-09-24 17:55:46.000000000 +0000 +++ hlint-3.1.6/src/Hint/NewType.hs 2020-05-13 11:33:34.000000000 +0000 @@ -5,24 +5,25 @@ quantified data types because it is not valid. -data Foo = Foo Int -- newtype Foo = Foo Int -data Foo = Foo Int deriving (Show, Eq) -- newtype Foo = Foo Int deriving (Show, Eq) -data Foo = Foo { field :: Int } deriving Show -- newtype Foo = Foo { field :: Int } deriving Show -data Foo a b = Foo a -- newtype Foo a b = Foo a +data Foo = Foo Int -- newtype Foo = Foo Int @NoRefactor: refactoring for "Use newtype" is not implemented +data Foo = Foo Int deriving (Show, Eq) -- newtype Foo = Foo Int deriving (Show, Eq) @NoRefactor +data Foo = Foo { field :: Int } deriving Show -- newtype Foo = Foo { field :: Int } deriving Show @NoRefactor +data Foo a b = Foo a -- newtype Foo a b = Foo a @NoRefactor data Foo = Foo { field1, field2 :: Int} -data S a = forall b . Show b => S b -{-# LANGUAGE RankNTypes #-}; data Foo = Foo (forall a. a) -- newtype Foo = Foo (forall a. a) +data S a = forall b . Show b => S b @NoRefactor: apply-refact 0.6 requires RankNTypes pragma +{-# LANGUAGE RankNTypes #-}; data S a = forall b . Show b => S b +{-# LANGUAGE RankNTypes #-}; data Foo = Foo (forall a. a) -- newtype Foo = Foo (forall a. a) @NoRefactor data Color a = Red a | Green a | Blue a data Pair a b = Pair a b data Foo = Bar data Foo a = Eq a => MkFoo a -data Foo a = () => Foo a -- newtype Foo a = Foo a -data X = Y {-# UNPACK #-} !Int -- newtype X = Y Int -data A = A {b :: !C} -- newtype A = A {b :: C} -data A = A Int# +data Foo a = () => Foo a -- newtype Foo a = Foo a @NoRefactor +data X = Y {-# UNPACK #-} !Int -- newtype X = Y Int @NoRefactor +data A = A {b :: !C} -- newtype A = A {b :: C} @NoRefactor +data A = A Int# @NoRefactor {-# LANGUAGE UnboxedTuples #-}; data WithAnn x = WithAnn (# Ann, x #) {-# LANGUAGE UnboxedTuples #-}; data WithAnn x = WithAnn {getWithAnn :: (# Ann, x #)} -data A = A () -- newtype A = A () +data A = A () -- newtype A = A () @NoRefactor newtype Foo = Foo Int deriving (Show, Eq) -- newtype Foo = Foo { getFoo :: Int } deriving (Show, Eq) -- newtype Foo = Foo Int deriving stock Show @@ -30,31 +31,31 @@ -} module Hint.NewType (newtypeHint) where -import Hint.Type (Idea, DeclHint', Note(DecreasesLaziness), ideaNote, ignoreNoSuggestion', suggestN') +import Hint.Type (Idea, DeclHint, Note(DecreasesLaziness), ideaNote, ignoreNoSuggestion, suggestN) import Data.List (isSuffixOf) -import HsDecls -import HsSyn +import GHC.Hs.Decls +import GHC.Hs import Outputable import SrcLoc -newtypeHint :: DeclHint' +newtypeHint :: DeclHint newtypeHint _ _ x = newtypeHintDecl x ++ newTypeDerivingStrategiesHintDecl x newtypeHintDecl :: LHsDecl GhcPs -> [Idea] newtypeHintDecl old | Just WarnNewtype{newDecl, insideType} <- singleSimpleField old - = [(suggestN' "Use newtype instead of data" old newDecl) + = [(suggestN "Use newtype instead of data" old newDecl) {ideaNote = [DecreasesLaziness | warnBang insideType]}] newtypeHintDecl _ = [] newTypeDerivingStrategiesHintDecl :: LHsDecl GhcPs -> [Idea] -newTypeDerivingStrategiesHintDecl decl@(LL _ (TyClD _ (DataDecl _ _ _ _ dataDef))) = - [ignoreNoSuggestion' "Use DerivingStrategies" decl | not $ isData dataDef, not $ hasAllStrategies dataDef] +newTypeDerivingStrategiesHintDecl decl@(L _ (TyClD _ (DataDecl _ _ _ _ dataDef))) = + [ignoreNoSuggestion "Use DerivingStrategies" decl | not $ isData dataDef, not $ hasAllStrategies dataDef] newTypeDerivingStrategiesHintDecl _ = [] hasAllStrategies :: HsDataDefn GhcPs -> Bool -hasAllStrategies (HsDataDefn _ NewType _ _ _ _ (LL _ xs)) = all hasStrategyClause xs +hasAllStrategies (HsDataDefn _ NewType _ _ _ _ (L _ xs)) = all hasStrategyClause xs hasAllStrategies _ = False isData :: HsDataDefn GhcPs -> Bool @@ -63,7 +64,7 @@ isData _ = False hasStrategyClause :: LHsDerivingClause GhcPs -> Bool -hasStrategyClause (LL _ (HsDerivingClause _ (Just _) _)) = True +hasStrategyClause (L _ (HsDerivingClause _ (Just _) _)) = True hasStrategyClause _ = False data WarnNewtype = WarnNewtype @@ -79,12 +80,12 @@ -- * Single record field constructors get newtyped - @data X = X {getX :: Int}@ -> @newtype X = X {getX :: Int}@ -- * All other declarations are ignored. singleSimpleField :: LHsDecl GhcPs -> Maybe WarnNewtype -singleSimpleField (LL loc (TyClD ext decl@(DataDecl _ _ _ _ dataDef@(HsDataDefn _ DataType _ _ _ [LL _ constructor] _)))) +singleSimpleField (L loc (TyClD ext decl@(DataDecl _ _ _ _ dataDef@(HsDataDefn _ DataType _ _ _ [L _ constructor] _)))) | Just inType <- simpleCons constructor = Just WarnNewtype - { newDecl = LL loc $ TyClD ext decl {tcdDataDefn = dataDef + { newDecl = L loc $ TyClD ext decl {tcdDataDefn = dataDef { dd_ND = NewType - , dd_cons = map (\(LL consloc x) -> LL consloc $ dropConsBang x) $ dd_cons dataDef + , dd_cons = map (\(L consloc x) -> L consloc $ dropConsBang x) $ dd_cons dataDef }} , insideType = inType } @@ -93,12 +94,12 @@ -- | Checks whether its argument is a \"simple constructor\" (see criteria in 'singleSimpleFieldNew') -- returning the type inside the constructor if it is. This is needed for strictness analysis. simpleCons :: ConDecl GhcPs -> Maybe (HsType GhcPs) -simpleCons (ConDeclH98 _ _ _ [] context (PrefixCon [LL _ inType]) _) +simpleCons (ConDeclH98 _ _ _ [] context (PrefixCon [L _ inType]) _) | emptyOrNoContext context , not $ isUnboxedTuple inType , not $ isHashy inType = Just inType -simpleCons (ConDeclH98 _ _ _ [] context (RecCon (LL _ [LL _ (ConDeclField _ [_] (LL _ inType) _)])) _) +simpleCons (ConDeclH98 _ _ _ [] context (RecCon (L _ [L _ (ConDeclField _ [_] (L _ inType) _)])) _) | emptyOrNoContext context , not $ isUnboxedTuple inType , not $ isHashy inType @@ -115,18 +116,18 @@ emptyOrNoContext :: Maybe (LHsContext GhcPs) -> Bool emptyOrNoContext Nothing = True -emptyOrNoContext (Just (LL _ [])) = True +emptyOrNoContext (Just (L _ [])) = True emptyOrNoContext _ = False -- | The \"Bang\" here refers to 'HsSrcBang', which notably also includes @UNPACK@ pragmas! dropConsBang :: ConDecl GhcPs -> ConDecl GhcPs dropConsBang decl@(ConDeclH98 _ _ _ _ _ (PrefixCon fields) _) = decl {con_args = PrefixCon $ map getBangType fields} -dropConsBang decl@(ConDeclH98 _ _ _ _ _ (RecCon (LL recloc conDeclFields)) _) = +dropConsBang decl@(ConDeclH98 _ _ _ _ _ (RecCon (L recloc conDeclFields)) _) = decl {con_args = RecCon $ cL recloc $ removeUnpacksRecords conDeclFields} where removeUnpacksRecords :: [LConDeclField GhcPs] -> [LConDeclField GhcPs] - removeUnpacksRecords = map (\(LL conDeclFieldLoc x) -> LL conDeclFieldLoc $ removeConDeclFieldUnpacks x) + removeUnpacksRecords = map (\(L conDeclFieldLoc x) -> L conDeclFieldLoc $ removeConDeclFieldUnpacks x) removeConDeclFieldUnpacks :: ConDeclField GhcPs -> ConDeclField GhcPs removeConDeclFieldUnpacks conDeclField@(ConDeclField _ _ fieldType _) = diff -Nru hlint-2.2.11/src/Hint/Pattern.hs hlint-3.1.6/src/Hint/Pattern.hs --- hlint-2.2.11/src/Hint/Pattern.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/Hint/Pattern.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,4 +1,4 @@ -{-# LANGUAGE ViewPatterns, PatternGuards #-} +{-# LANGUAGE ViewPatterns, PatternGuards, TypeFamilies #-} {- Improve the structure of code @@ -16,7 +16,7 @@ | c <- f b = c foo x = yes x x where yes x y = if a then b else if c then d else e -- yes x y ; | a = b ; | c = d ; | otherwise = e foo x | otherwise = y -- foo x = y -foo x = x + x where -- foo x = x + x +foo x = x + x where -- @NoRefactor: refactoring for "Redundant where" is not implemented foo x | a = b | True = d -- foo x | a = b ; | otherwise = d foo (Bar _ _ _ _) = x -- Bar{} foo (Bar _ x _ _) = x @@ -25,38 +25,41 @@ foo = case v of v -> x -- x foo = case v of z -> z foo = case v of _ | False -> x +foo x | x < -2 * 3 = 4 @NoRefactor: ghc-exactprint bug; -2 becomes 2. foo = case v of !True -> x -- True -foo = case v of !(Just x) -> x -- (Just x) -foo = case v of !(x : xs) -> x -- (x:xs) -foo = case v of !1 -> x -- 1 -foo = case v of !x -> x -foo = case v of !(I# x) -> y -- (I# x) +{-# LANGUAGE BangPatterns #-}; foo = case v of !True -> x -- True +{-# LANGUAGE BangPatterns #-}; foo = case v of !(Just x) -> x -- (Just x) +{-# LANGUAGE BangPatterns #-}; foo = case v of !(x : xs) -> x -- (x:xs) +{-# LANGUAGE BangPatterns #-}; foo = case v of !1 -> x -- 1 +{-# LANGUAGE BangPatterns #-}; foo = case v of !x -> x +{-# LANGUAGE BangPatterns #-}; foo = case v of !(I# x) -> y -- (I# x) foo = let ~x = 1 in y -- x foo = let ~(x:xs) = y in z -foo = let !x = undefined in y -foo = let !(I# x) = 4 in x -foo = let !(Just x) = Nothing in 3 -foo = 1 where f !False = 2 -- False -foo = 1 where !False = True -foo = 1 where g (Just !True) = Nothing -- True -foo = 1 where Just !True = Nothing -foo otherwise = 1 -- _ +{-# LANGUAGE BangPatterns #-}; foo = let !x = undefined in y +{-# LANGUAGE BangPatterns #-}; foo = let !(I# x) = 4 in x +{-# LANGUAGE BangPatterns #-}; foo = let !(Just x) = Nothing in 3 +{-# LANGUAGE BangPatterns #-}; foo = 1 where f !False = 2 -- False +{-# LANGUAGE BangPatterns #-}; foo = 1 where !False = True +{-# LANGUAGE BangPatterns #-}; foo = 1 where g (Just !True) = Nothing -- True +{-# LANGUAGE BangPatterns #-}; foo = 1 where Just !True = Nothing +foo otherwise = 1 -- _ @NoRefactor foo ~x = y -- x {-# LANGUAGE Strict #-} foo ~x = y -foo !(x, y) = x -- (x, y) -foo ![x] = x -- [x] +{-# LANGUAGE BangPatterns #-}; foo !(x, y) = x -- (x, y) +{-# LANGUAGE BangPatterns #-}; foo ![x] = x -- [x] foo !Bar { bar = x } = x -- Bar { bar = x } -l !(() :: ()) = x -- (() :: ()) +{-# LANGUAGE BangPatterns #-}; l !(() :: ()) = x -- (() :: ()) foo x@_ = x -- x foo x@Foo = x +otherwise = True -} module Hint.Pattern(patternHint) where -import Hint.Type(DeclHint',Idea,ghcAnnotations,ideaTo,toSS',toRefactSrcSpan,ghcSpanToHSE,suggest',warn') -import Data.Generics.Uniplate.Operations +import Hint.Type(DeclHint,Idea,ghcAnnotations,ideaTo,toSS,toRefactSrcSpan,suggest,suggestRemove,warn) +import Data.Generics.Uniplate.DataOnly import Data.Function import Data.List.Extra import Data.Tuple @@ -65,7 +68,7 @@ import Refact.Types hiding (RType(Pattern, Match), SrcSpan) import qualified Refact.Types as R (RType(Pattern, Match), SrcSpan) -import HsSyn +import GHC.Hs import SrcLoc import RdrName import OccName @@ -73,23 +76,25 @@ import BasicTypes import GHC.Util +import Language.Haskell.GhclibParserEx.GHC.Hs.Pat import Language.Haskell.GhclibParserEx.GHC.Hs.Expr +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader -patternHint :: DeclHint' +patternHint :: DeclHint patternHint _scope modu x = concatMap (uncurry hints . swap) (asPattern x) ++ -- PatBind (used in 'let' and 'where') contains lazy-by-default -- patterns, everything else is strict. - concatMap (patHint strict False) (located [p | PatBind _ p _ _ <- universeBi x :: [HsBind GhcPs]]) ++ - concatMap (patHint strict True) (located (universeBi $ transformBi noPatBind x)) ++ + concatMap (patHint strict False) [p | PatBind _ p _ _ <- universeBi x :: [HsBind GhcPs]] ++ + concatMap (patHint strict True) (universeBi $ transformBi noPatBind x) ++ concatMap expHint (universeBi x) where - located ps = [p | p@XPat{} <- ps] -- restrict attention to patterns with locs - exts = nubOrd $ concatMap snd (langExts (pragmas (ghcAnnotations modu))) -- language extensions enabled at source + exts = nubOrd $ concatMap snd (languagePragmas (pragmas (ghcAnnotations modu))) -- language extensions enabled at source strict = "Strict" `elem` exts noPatBind :: LHsBind GhcPs -> LHsBind GhcPs - noPatBind (LL loc a@PatBind{}) = cL loc a{pat_lhs=noLoc (WildPat noExt)} + noPatBind (L loc a@PatBind{}) = L loc a{pat_lhs=noLoc (WildPat noExtField)} noPatBind x = x {- @@ -105,14 +110,14 @@ -} hints :: (String -> Pattern -> [Refactoring R.SrcSpan] -> Idea) -> Pattern -> [Idea] -hints gen (Pattern l rtype pat (GRHSs _ [LL _ (GRHS _ [] bod)] bind)) - | length guards > 2 = [gen "Use guards" (Pattern l rtype pat (GRHSs noExt guards bind)) [refactoring]] +hints gen (Pattern l rtype pat (GRHSs _ [L _ (GRHS _ [] bod)] bind)) + | length guards > 2 = [gen "Use guards" (Pattern l rtype pat (GRHSs noExtField guards bind)) [refactoring]] where rawGuards :: [(LHsExpr GhcPs, LHsExpr GhcPs)] rawGuards = asGuards bod mkGuard :: LHsExpr GhcPs -> (LHsExpr GhcPs -> GRHS GhcPs (LHsExpr GhcPs)) - mkGuard a = GRHS noExt [noLoc $ BodyStmt noExt a noSyntaxExpr noSyntaxExpr] + mkGuard a = GRHS noExtField [noLoc $ BodyStmt noExtField a noSyntaxExpr noSyntaxExpr] guards :: [LGRHS GhcPs (LHsExpr GhcPs)] guards = map (noLoc . uncurry mkGuard) rawGuards @@ -123,8 +128,7 @@ -- Check if the expression has been injected or is natural. zipWith checkLoc ps ['1' .. '9'] where - checkLoc p@(LL l _) v = if l == noSrcSpan then Left p else Right (c ++ [v], toSS' p) - checkLoc _ v = undefined -- {-# COMPLETE LL #-} + checkLoc p@(L l _) v = if l == noSrcSpan then Left p else Right (c ++ [v], toSS p) patSubts = case pat of @@ -138,100 +142,104 @@ toString (Left e) = e toString (Right (v, _)) = strToVar v toString' (Left e) = e - toString' (Right (v, _)) = strToPat' v + toString' (Right (v, _)) = strToPat v - template = fromMaybe "" $ ideaTo (gen "" (Pattern l rtype (map toString' patSubts) (GRHSs noExt templateGuards bind)) []) + template = fromMaybe "" $ ideaTo (gen "" (Pattern l rtype (map toString' patSubts) (GRHSs noExtField templateGuards bind)) []) f :: [Either a (String, R.SrcSpan)] -> [(String, R.SrcSpan)] f = rights - refactoring = Replace rtype (toRefactSrcSpan$ ghcSpanToHSE l) (f patSubts ++ f guardSubts ++ f exprSubts) template -hints gen (Pattern l t pats o@(GRHSs _ [LL _ (GRHS _ [test] bod)] bind)) + refactoring = Replace rtype (toRefactSrcSpan l) (f patSubts ++ f guardSubts ++ f exprSubts) template +hints gen (Pattern l t pats o@(GRHSs _ [L _ (GRHS _ [test] bod)] bind)) | unsafePrettyPrint test `elem` ["otherwise", "True"] - = [gen "Redundant guard" (Pattern l t pats o{grhssGRHSs=[noLoc (GRHS noExt [] bod)]}) [Delete Stmt (toSS' test)]] -hints gen (Pattern l t pats bod@(GRHSs _ _ binds)) | f binds - = [gen "Redundant where" (Pattern l t pats bod{grhssLocalBinds=noLoc (EmptyLocalBinds noExt)}) []] + = [gen "Redundant guard" (Pattern l t pats o{grhssGRHSs=[noLoc (GRHS noExtField [] bod)]}) [Delete Stmt (toSS test)]] +hints _ (Pattern l t pats bod@(GRHSs _ _ binds)) | f binds + = [suggestRemove "Redundant where" whereSpan "where" [ {- TODO refactoring for redundant where -} ]] where f :: LHsLocalBinds GhcPs -> Bool - f (LL _ (HsValBinds _ (ValBinds _ bag _))) = isEmptyBag bag - f (LL _ (HsIPBinds _ (IPBinds _ l))) = null l + f (L _ (HsValBinds _ (ValBinds _ bag _))) = isEmptyBag bag + f (L _ (HsIPBinds _ (IPBinds _ l))) = null l f _ = False -hints gen (Pattern l t pats o@(GRHSs _ (unsnoc -> Just (gs, LL _ (GRHS _ [test] bod))) binds)) + whereSpan = case l of + UnhelpfulSpan s -> UnhelpfulSpan s + RealSrcSpan s -> + let end = realSrcSpanEnd s + start = mkRealSrcLoc (srcSpanFile s) (srcLocLine end) (srcLocCol end - 5) + in RealSrcSpan (mkRealSrcSpan start end) +hints gen (Pattern l t pats o@(GRHSs _ (unsnoc -> Just (gs, L _ (GRHS _ [test] bod))) binds)) | unsafePrettyPrint test == "True" - = let tag = noLoc (mkRdrUnqual $ mkVarOcc "otherwise") - otherwise_ = noLoc $ BodyStmt noExt (noLoc (HsVar noExt tag)) noSyntaxExpr noSyntaxExpr in - [gen "Use otherwise" (Pattern l t pats o{grhssGRHSs = gs ++ [noLoc (GRHS noExt [otherwise_] bod)]}) [Replace Expr (toSS' test) [] "otherwise"]] + = let otherwise_ = noLoc $ BodyStmt noExtField (strToVar "otherwise") noSyntaxExpr noSyntaxExpr in + [gen "Use otherwise" (Pattern l t pats o{grhssGRHSs = gs ++ [noLoc (GRHS noExtField [otherwise_] bod)]}) [Replace Expr (toSS test) [] "otherwise"]] hints _ _ = [] asGuards :: LHsExpr GhcPs -> [(LHsExpr GhcPs, LHsExpr GhcPs)] -asGuards (LL _ (HsPar _ x)) = asGuards x -asGuards (LL _ (HsIf _ _ a b c)) = (a, b) : asGuards c -asGuards x = [(noLoc (HsVar noExt (noLoc (mkRdrUnqual $ mkVarOcc "otherwise"))), x)] +asGuards (L _ (HsPar _ x)) = asGuards x +asGuards (L _ (HsIf _ _ a b c)) = (a, b) : asGuards c +asGuards x = [(strToVar "otherwise", x)] -data Pattern = Pattern SrcSpan R.RType [Pat GhcPs] (GRHSs GhcPs (LHsExpr GhcPs)) +data Pattern = Pattern SrcSpan R.RType [LPat GhcPs] (GRHSs GhcPs (LHsExpr GhcPs)) -- Invariant: Number of patterns may not change asPattern :: LHsDecl GhcPs -> [(Pattern, String -> Pattern -> [Refactoring R.SrcSpan] -> Idea)] -asPattern (LL loc x) = concatMap decl (universeBi x) +asPattern (L loc x) = concatMap decl (universeBi x) where decl :: HsBind GhcPs -> [(Pattern, String -> Pattern -> [Refactoring R.SrcSpan] -> Idea)] - decl o@(PatBind _ pat rhs _) = [(Pattern loc Bind [pat] rhs, \msg (Pattern _ _ [pat] rhs) rs -> suggest' msg (cL loc o :: LHsBind GhcPs) (noLoc (PatBind noExt pat rhs ([], [])) :: LHsBind GhcPs) rs)] - decl (FunBind _ _ (MG _ (LL _ xs) _) _ _) = map match xs + decl o@(PatBind _ pat rhs _) = [(Pattern loc Bind [pat] rhs, \msg (Pattern _ _ [pat] rhs) rs -> suggest msg (L loc o :: LHsBind GhcPs) (noLoc (PatBind noExtField pat rhs ([], [])) :: LHsBind GhcPs) rs)] + decl (FunBind _ _ (MG _ (L _ xs) _) _ _) = map match xs decl _ = [] match :: LMatch GhcPs (LHsExpr GhcPs) -> (Pattern, String -> Pattern -> [Refactoring R.SrcSpan] -> Idea) - match o@(LL loc (Match _ ctx pats grhss)) = (Pattern loc R.Match pats grhss, \msg (Pattern _ _ pats grhss) rs -> suggest' msg o (noLoc (Match noExt ctx pats grhss) :: LMatch GhcPs (LHsExpr GhcPs)) rs) - match _ = undefined -- {-# COMPLETE LL #-} -asPattern _ = [] -- {-# COMPLETE LL #-} + match o@(L loc (Match _ ctx pats grhss)) = (Pattern loc R.Match pats grhss, \msg (Pattern _ _ pats grhss) rs -> suggest msg o (noLoc (Match noExtField ctx pats grhss) :: LMatch GhcPs (LHsExpr GhcPs)) rs) + match _ = undefined -- {-# COMPLETE L #-} -- First Bool is if 'Strict' is a language extension. Second Bool is -- if this pattern in this context is going to be evaluated strictly. -patHint :: Bool -> Bool -> Pat GhcPs -> [Idea] -patHint _ _ o@(LL _ (ConPatIn name (PrefixCon args))) - | length args >= 3 && all isPWildCard' args = - let rec_fields = HsRecFields [] Nothing :: HsRecFields GhcPs (Pat GhcPs) - new = ConPatIn name (RecCon rec_fields) :: Pat GhcPs +patHint :: Bool -> Bool -> LPat GhcPs -> [Idea] +patHint _ _ o@(L _ (ConPatIn name (PrefixCon args))) + | length args >= 3 && all isPWildcard args = + let rec_fields = HsRecFields [] Nothing :: HsRecFields GhcPs (LPat GhcPs) + new = noLoc $ ConPatIn name (RecCon rec_fields) :: LPat GhcPs in - [suggest' "Use record patterns" o new [Replace R.Pattern (toSS' o) [] (unsafePrettyPrint new)]] -patHint _ _ o@(LL _ (VarPat _ (L _ name))) + [suggest "Use record patterns" o new [Replace R.Pattern (toSS o) [] (unsafePrettyPrint new)]] +patHint _ _ o@(L _ (VarPat _ (L _ name))) | occNameString (rdrNameOcc name) == "otherwise" = - [warn' "Used otherwise as a pattern" o (noLoc (WildPat noExt) :: Pat GhcPs) []] -patHint lang strict o@(LL _ (BangPat _ (LL _ x))) - | strict, f x = [warn' "Redundant bang pattern" o x [r]] + [warn "Used otherwise as a pattern" o (noLoc (WildPat noExtField) :: LPat GhcPs) []] +patHint lang strict o@(L _ (BangPat _ pat@(L _ x))) + | strict, f x = [warn "Redundant bang pattern" o (noLoc x :: LPat GhcPs) [r]] where f :: Pat GhcPs -> Bool - f (ParPat _ (LL _ x)) = f x - f (AsPat _ _ (LL _ x)) = f x + f (ParPat _ (L _ x)) = f x + f (AsPat _ _ (L _ x)) = f x f LitPat {} = True f NPat {} = True f ConPatIn {} = True f TuplePat {} = True f ListPat {} = True - f (SigPat _ (LL _ p) _) = f p + f (SigPat _ (L _ p) _) = f p f _ = False - r = Replace R.Pattern (toSS' o) [("x", toSS' x)] "x" -patHint False _ o@(LL _ (LazyPat _ (LL _ x))) - | f x = [warn' "Redundant irrefutable pattern" o x [r]] + r = Replace R.Pattern (toSS o) [("x", toSS pat)] "x" +patHint False _ o@(L _ (LazyPat _ pat@(L _ x))) + | f x = [warn "Redundant irrefutable pattern" o (noLoc x :: LPat GhcPs) [r]] where f :: Pat GhcPs -> Bool - f (ParPat _ (LL _ x)) = f x - f (AsPat _ _ (LL _ x)) = f x + f (ParPat _ (L _ x)) = f x + f (AsPat _ _ (L _ x)) = f x f WildPat{} = True f VarPat{} = True f _ = False - r = Replace R.Pattern (toSS' o) [("x", toSS' x)] "x" -patHint _ _ o@(LL _ (AsPat _ v (LL _ (WildPat _)))) = - [warn' "Redundant as-pattern" o v []] + r = Replace R.Pattern (toSS o) [("x", toSS pat)] "x" +patHint _ _ o@(L _ (AsPat _ v (L _ (WildPat _)))) = + [warn "Redundant as-pattern" o v []] patHint _ _ _ = [] expHint :: LHsExpr GhcPs -> [Idea] -- Note the 'FromSource' in these equations (don't warn on generated match groups). -expHint o@(LL _ (HsCase _ _ (MG _ (L _ [LL _ (Match _ CaseAlt [LL _ (WildPat _)] (GRHSs _ [LL _ (GRHS _ [] e)] (LL _ (EmptyLocalBinds _)))) ]) FromSource ))) = - [suggest' "Redundant case" o e [r]] +expHint o@(L _ (HsCase _ _ (MG _ (L _ [L _ (Match _ CaseAlt [L _ (WildPat _)] (GRHSs _ [L _ (GRHS _ [] e)] (L _ (EmptyLocalBinds _)))) ]) FromSource ))) = + [suggest "Redundant case" o e [r]] where - r = Replace Expr (toSS' o) [("x", toSS' e)] "x" -expHint o@(LL _ (HsCase _ (LL _ (HsVar _ (L _ x))) (MG _ (L _ [LL _ (Match _ CaseAlt [LL _ (VarPat _ (L _ y))] (GRHSs _ [LL _ (GRHS _ [] e)] (LL _ (EmptyLocalBinds _)))) ]) FromSource ))) - | occNameString (rdrNameOcc x) == occNameString (rdrNameOcc y) = - [suggest' "Redundant case" o e [r]] + r = Replace Expr (toSS o) [("x", toSS e)] "x" +expHint o@(L _ (HsCase _ (L _ (HsVar _ (L _ x))) (MG _ (L _ [L _ (Match _ CaseAlt [L _ (VarPat _ (L _ y))] (GRHSs _ [L _ (GRHS _ [] e)] (L _ (EmptyLocalBinds _)))) ]) FromSource ))) + | occNameStr x == occNameStr y = + [suggest "Redundant case" o e [r]] where - r = Replace Expr (toSS' o) [("x", toSS' e)] "x" + r = Replace Expr (toSS o) [("x", toSS e)] "x" expHint _ = [] diff -Nru hlint-2.2.11/src/Hint/Pragma.hs hlint-3.1.6/src/Hint/Pragma.hs --- hlint-2.2.11/src/Hint/Pragma.hs 2020-02-09 21:16:43.000000000 +0000 +++ hlint-3.1.6/src/Hint/Pragma.hs 2020-06-24 11:09:26.000000000 +0000 @@ -14,10 +14,12 @@ {-# OPTIONS -cpp #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS_YHC -cpp #-} {-# OPTIONS_GHC -XFoo #-} -- {-# LANGUAGE Foo #-} -{-# OPTIONS_GHC -fglasgow-exts #-} -- ??? +{-# OPTIONS_GHC -fglasgow-exts #-} -- ??? @NoRefactor +{-# LANGUAGE RebindableSyntax, EmptyCase, RebindableSyntax #-} -- {-# LANGUAGE RebindableSyntax, EmptyCase #-} {-# LANGUAGE RebindableSyntax, EmptyCase, DuplicateRecordFields, RebindableSyntax #-} -- {-# LANGUAGE RebindableSyntax, EmptyCase, DuplicateRecordFields #-} {-# LANGUAGE RebindableSyntax #-} -{-# OPTIONS_GHC -cpp -foo #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS_GHC -foo #-} +{-# OPTIONS_GHC -cpp -foo #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS_GHC -foo #-} @NoRefactor -foo is not a valid flag +{-# OPTIONS_GHC -cpp -w #-} -- {-# LANGUAGE CPP #-} {-# OPTIONS_GHC -w #-} {-# OPTIONS_GHC -cpp #-} \ {-# LANGUAGE CPP, Text #-} -- {-# LANGUAGE RebindableSyntax #-} \ @@ -30,7 +32,7 @@ module Hint.Pragma(pragmaHint) where -import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),toSS',rawIdea',prettyExtension,glasgowExts) +import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),toSS,rawIdea) import Data.List.Extra import qualified Data.List.NonEmpty as NE import Data.Maybe @@ -41,18 +43,19 @@ import SrcLoc import GHC.Util +import DynFlags pragmaHint :: ModuHint pragmaHint _ modu = let ps = pragmas (ghcAnnotations modu) opts = flags ps - lang = langExts ps in + lang = languagePragmas ps in languageDupes lang ++ optToPragma opts lang optToPragma :: [(Located AnnotationComment, [String])] -> [(Located AnnotationComment, [String])] -> [Idea] -optToPragma flags langExts = +optToPragma flags languagePragmas = [pragmaIdea (OptionsToComment (fst <$> old2) ys rs) | Just old2 <- [NE.nonEmpty old]] where (old, new, ns, rs) = @@ -60,17 +63,17 @@ | old <- flags, Just (new, ns) <- [optToLanguage old ls] , let r = mkRefact old new ns] - ls = concatMap snd langExts + ls = concatMap snd languagePragmas ns2 = nubOrd (concat ns) \\ ls - ys = [mkLangExts noSrcSpan ns2 | ns2 /= []] ++ catMaybes new + ys = [mkLanguagePragmas noSrcSpan ns2 | ns2 /= []] ++ catMaybes new mkRefact :: (Located AnnotationComment, [String]) -> Maybe (Located AnnotationComment) -> [String] -> Refactoring R.SrcSpan mkRefact old (maybe "" comment -> new) ns = - let ns' = map (\n -> comment (mkLangExts noSrcSpan [n])) ns - in ModifyComment (toSS' (fst old)) (intercalate "\n" (filter (not . null) (new : ns'))) + let ns' = map (\n -> comment (mkLanguagePragmas noSrcSpan [n])) ns + in ModifyComment (toSS (fst old)) (intercalate "\n" (filter (not . null) (ns' `snoc` new))) data PragmaIdea = SingleComment (Located AnnotationComment) (Located AnnotationComment) | MultiComment (Located AnnotationComment) (Located AnnotationComment) (Located AnnotationComment) @@ -81,26 +84,26 @@ case pidea of SingleComment old new -> mkFewer (getLoc old) (comment old) (Just $ comment new) [] - [ModifyComment (toSS' old) (comment new)] + [ModifyComment (toSS old) (comment new)] MultiComment repl delete new -> mkFewer (getLoc repl) (f [repl, delete]) (Just $ comment new) [] - [ ModifyComment (toSS' repl) (comment new) - , ModifyComment (toSS' delete) ""] + [ ModifyComment (toSS repl) (comment new) + , ModifyComment (toSS delete) ""] OptionsToComment old new r -> mkLanguage (getLoc . NE.head $ old) (f $ NE.toList old) (Just $ f new) [] r where f = unlines . map comment - mkFewer = rawIdea' Hint.Type.Warning "Use fewer LANGUAGE pragmas" - mkLanguage = rawIdea' Hint.Type.Warning "Use LANGUAGE pragmas" + mkFewer = rawIdea Hint.Type.Warning "Use fewer LANGUAGE pragmas" + mkLanguage = rawIdea Hint.Type.Warning "Use LANGUAGE pragmas" languageDupes :: [(Located AnnotationComment, [String])] -> [Idea] -languageDupes ( (a@(LL l _), les) : cs ) = +languageDupes ( (a@(L l _), les) : cs ) = (if nubOrd les /= les - then [pragmaIdea (SingleComment a (mkLangExts l $ nubOrd les))] - else [pragmaIdea (MultiComment a b (mkLangExts l (nubOrd $ les ++ les'))) | ( b@(LL _ _), les' ) <- cs, not $ null $ intersect les les'] + then [pragmaIdea (SingleComment a (mkLanguagePragmas l $ nubOrd les))] + else [pragmaIdea (MultiComment a b (mkLanguagePragmas l (nubOrd $ les ++ les'))) | ( b@(L _ _), les' ) <- cs, not $ disjoint les les'] ) ++ languageDupes cs languageDupes _ = [] @@ -108,7 +111,7 @@ strToLanguage :: String -> Maybe [String] strToLanguage "-cpp" = Just ["CPP"] strToLanguage x | "-X" `isPrefixOf` x = Just [drop 2 x] -strToLanguage "-fglasgow-exts" = Just $ map prettyExtension glasgowExts +strToLanguage "-fglasgow-exts" = Just $ map show glasgowExtsFlags strToLanguage _ = Nothing -- In 'optToLanguage p langexts', 'p' is an 'OPTIONS_GHC' pragma, @@ -126,12 +129,12 @@ optToLanguage :: (Located AnnotationComment, [String]) -> [String] -> Maybe (Maybe (Located AnnotationComment), [String]) -optToLanguage (LL loc _, flags) langExts +optToLanguage (L loc _, flags) languagePragmas | any isJust vs = -- 'ls' is a list of language features enabled by this -- OPTIONS_GHC pragma that are not enabled by LANGUAGE pragmas -- in this module. - let ls = filter (not . (`elem` langExts)) (concat $ catMaybes vs) in + let ls = filter (not . (`elem` languagePragmas)) (concat $ catMaybes vs) in Just (res, ls) where -- Try reinterpreting each flag as a list of language features diff -Nru hlint-2.2.11/src/Hint/Restrict.hs hlint-3.1.6/src/Hint/Restrict.hs --- hlint-2.2.11/src/Hint/Restrict.hs 2020-01-27 16:14:58.000000000 +0000 +++ hlint-3.1.6/src/Hint/Restrict.hs 2020-06-14 18:45:05.000000000 +0000 @@ -2,6 +2,7 @@ {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ViewPatterns #-} module Hint.Restrict(restrictHint) where @@ -11,30 +12,38 @@ foo = unsafePerformIO -- foo = bar `unsafePerformIO` baz -- module Util where otherFunc = unsafePerformIO $ print 1 -- -module Util where exitMessageImpure = unsafePerformIO $ print 1 +module Util where exitMessageImpure = System.IO.Unsafe.unsafePerformIO $ print 1 foo = unsafePerformOI +import Data.List.NonEmpty as NE \ +foo = NE.nub (NE.fromList [1, 2, 3]) -- +import Hypothetical.Module \ +foo = nub s -} -import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),warn',rawIdea') +import Hint.Type(ModuHint,ModuleEx(..),Idea(..),Severity(..),warn,rawIdea) import Config.Type -import Data.Generics.Uniplate.Operations +import Data.Generics.Uniplate.DataOnly +import qualified Data.List.NonEmpty as NonEmpty import qualified Data.Set as Set import qualified Data.Map as Map -import Data.List +import Data.List.Extra import Data.Maybe import Data.Semigroup +import Data.Tuple.Extra import Control.Applicative import Control.Monad import Prelude -import HsSyn +import GHC.Hs import RdrName import ApiAnnotation import Module import SrcLoc import OccName +import Language.Haskell.GhclibParserEx.GHC.Hs +import Language.Haskell.GhclibParserEx.GHC.Types.Name.Reader import GHC.Util -- FIXME: The settings should be partially applied, but that's hard to orchestrate right now @@ -43,13 +52,13 @@ let anns = ghcAnnotations m ps = pragmas anns opts = flags ps - exts = langExts ps in - checkPragmas modu opts exts restrict ++ - maybe [] (checkImports modu $ hsmodImports (unLoc (ghcModule m))) (Map.lookup RestrictModule restrict) ++ - maybe [] (checkFunctions modu $ hsmodDecls (unLoc (ghcModule m))) (Map.lookup RestrictFunction restrict) + exts = languagePragmas ps in + checkPragmas modu opts exts rOthers ++ + maybe [] (checkImports modu $ hsmodImports (unLoc (ghcModule m))) (Map.lookup RestrictModule rOthers) ++ + checkFunctions scope modu (hsmodDecls (unLoc (ghcModule m))) rFunction where modu = modName (ghcModule m) - restrict = restrictions settings + (rFunction, rOthers) = restrictions settings --------------------------------------------------------------------- -- UTILITIES @@ -60,19 +69,36 @@ ,riBadIdents :: [String] ,riMessage :: Maybe String } + instance Semigroup RestrictItem where RestrictItem x1 x2 x3 x4 <> RestrictItem y1 y2 y3 y4 = RestrictItem (x1<>y1) (x2<>y2) (x3<>y3) (x4<>y4) -instance Monoid RestrictItem where - mempty = RestrictItem [] [] [] Nothing - mappend = (<>) -restrictions :: [Setting] -> Map.Map RestrictType (Bool, Map.Map String RestrictItem) -restrictions settings = Map.map f $ Map.fromListWith (++) [(restrictType x, [x]) | SettingRestrict x <- settings] +-- Contains a map from module (Nothing if the rule is unqualified) to (within, message), so that we can +-- distinguish functions with the same name. +-- For example, this allows us to have separate rules for "Data.Map.fromList" and "Data.Set.fromList". +-- Using newtype rather than type because we want to define (<>) as 'Map.unionWith (<>)'. +newtype RestrictFunction = RestrictFun (Map.Map (Maybe String) ([(String, String)], Maybe String)) + +instance Semigroup RestrictFunction where + RestrictFun m1 <> RestrictFun m2 = RestrictFun (Map.unionWith (<>) m1 m2) + +type RestrictFunctions = (Bool, Map.Map String RestrictFunction) +type OtherRestrictItems = Map.Map RestrictType (Bool, Map.Map String RestrictItem) + +restrictions :: [Setting] -> (RestrictFunctions, OtherRestrictItems) +restrictions settings = (rFunction, rOthers) where + (map snd -> rfs, ros) = partition ((== RestrictFunction) . fst) [(restrictType x, x) | SettingRestrict x <- settings] + rFunction = (all restrictDefault rfs, Map.fromListWith (<>) [mkRf s r | r <- rfs, s <- restrictName r]) + mkRf s Restrict{..} = (name, RestrictFun $ Map.singleton modu (restrictWithin, restrictMessage)) + where + -- Parse module and name from s. module = Nothing if the rule is unqualified. + (modu, name) = first (fmap NonEmpty.init . NonEmpty.nonEmpty) (breakEnd (== '.') s) + + rOthers = Map.map f $ Map.fromListWith (++) (map (second pure) ros) f rs = (all restrictDefault rs ,Map.fromListWith (<>) [(s, RestrictItem restrictAs restrictWithin restrictBadIdents restrictMessage) | Restrict{..} <- rs, s <- restrictName]) - ideaMessage :: Maybe String -> Idea -> Idea ideaMessage (Just message) w = w{ideaNote=[Note message]} ideaMessage Nothing w = w{ideaNote=[noteMayBreak]} @@ -83,8 +109,8 @@ noteMayBreak :: Note noteMayBreak = Note "may break the code" -within :: String -> String -> RestrictItem -> Bool -within modu func RestrictItem{..} = any (\(a,b) -> (a == modu || a == "") && (b == func || b == "")) riWithin +within :: String -> String -> [(String, String)] -> Bool +within modu func = any (\(a,b) -> (a == modu || a == "") && (b == func || b == "")) --------------------------------------------------------------------- -- CHECKS @@ -98,25 +124,25 @@ f RestrictFlag "flags" flags ++ f RestrictExtension "extensions" exts where f tag name xs = - [(if null good then ideaNoTo else id) $ notes $ rawIdea' Hint.Type.Warning ("Avoid restricted " ++ name) l c Nothing [] [] + [(if null good then ideaNoTo else id) $ notes $ rawIdea Hint.Type.Warning ("Avoid restricted " ++ name) l c Nothing [] [] | Just (def, mp) <- [Map.lookup tag mps] , (L l (AnnBlockComment c), les) <- xs , let (good, bad) = partition (isGood def mp) les , let note = maybe noteMayBreak Note . (=<<) riMessage . flip Map.lookup mp , let notes w = w {ideaNote=note <$> bad} , not $ null bad] - isGood def mp x = maybe def (within modu "") $ Map.lookup x mp + isGood def mp x = maybe def (within modu "" . riWithin) $ Map.lookup x mp checkImports :: String -> [LImportDecl GhcPs] -> (Bool, Map.Map String RestrictItem) -> [Idea] checkImports modu imp (def, mp) = [ ideaMessage riMessage - $ if | not allowImport -> ideaNoTo $ warn' "Avoid restricted module" i i [] - | not allowIdent -> ideaNoTo $ warn' "Avoid restricted identifiers" i i [] - | not allowQual -> warn' "Avoid restricted qualification" i (noLoc $ (unLoc i){ ideclAs=noLoc . mkModuleName <$> listToMaybe riAs} :: Located (ImportDecl GhcPs)) [] + $ if | not allowImport -> ideaNoTo $ warn "Avoid restricted module" i i [] + | not allowIdent -> ideaNoTo $ warn "Avoid restricted identifiers" i i [] + | not allowQual -> warn "Avoid restricted qualification" i (noLoc $ (unLoc i){ ideclAs=noLoc . mkModuleName <$> listToMaybe riAs} :: Located (ImportDecl GhcPs)) [] | otherwise -> error "checkImports: unexpected case" - | i@(LL _ ImportDecl {..}) <- imp - , let ri@RestrictItem{..} = Map.findWithDefault (RestrictItem [] [("","") | def] [] Nothing) (moduleNameString (unLoc ideclName)) mp - , let allowImport = within modu "" ri + | i@(L _ ImportDecl {..}) <- imp + , let RestrictItem{..} = Map.findWithDefault (RestrictItem [] [("","") | def] [] Nothing) (moduleNameString (unLoc ideclName)) mp + , let allowImport = within modu "" riWithin , let allowIdent = Set.disjoint (Set.fromList riBadIdents) (Set.fromList (maybe [] (\(b, lxs) -> if b then [] else concatMap (importListToIdents . unLoc) (unLoc lxs)) ideclHiding)) @@ -145,12 +171,23 @@ fromId (Orig _ n) = Just $ occNameString n fromId (Exact _) = Nothing -checkFunctions :: String -> [LHsDecl GhcPs] -> (Bool, Map.Map String RestrictItem) -> [Idea] -checkFunctions modu decls (def, mp) = - [ (ideaMessage riMessage $ ideaNoTo $ warn' "Avoid restricted function" x x []){ideaDecl = [dname]} +checkFunctions :: Scope -> String -> [LHsDecl GhcPs] -> RestrictFunctions -> [Idea] +checkFunctions scope modu decls (def, mp) = + [ (ideaMessage message $ ideaNoTo $ warn "Avoid restricted function" x x []){ideaDecl = [dname]} | d <- decls , let dname = fromMaybe "" (declName d) , x <- universeBi d :: [Located RdrName] - , let ri@RestrictItem{..} = Map.findWithDefault (RestrictItem [] [("","") | def] [] Nothing) (occNameString (rdrNameOcc (unLoc x))) mp - , not $ within modu dname ri + , let xMods = possModules scope x + , let (withins, message) = fromMaybe ([("","") | def], Nothing) (findFunction x xMods) + , not $ within modu dname withins ] + where + -- Returns Just iff there are rules for x, which are either unqualified, or qualified with a module that is + -- one of x's possible modules. + -- If there are multiple matching rules (e.g., there's both an unqualified version and a qualified version), their + -- withins and messages are concatenated with (<>). + findFunction :: Located RdrName -> [ModuleName] -> Maybe ([(String, String)], Maybe String) + findFunction (rdrNameStr -> x) (map moduleNameString -> possMods) + | Just (RestrictFun mp) <- Map.lookup x mp = + fmap sconcat . NonEmpty.nonEmpty . Map.elems $ Map.filterWithKey (const . maybe True (`elem` possMods)) mp + | otherwise = Nothing diff -Nru hlint-2.2.11/src/Hint/Smell.hs hlint-3.1.6/src/Hint/Smell.hs --- hlint-2.2.11/src/Hint/Smell.hs 2019-10-22 19:47:53.000000000 +0000 +++ hlint-3.1.6/src/Hint/Smell.hs 2020-06-14 18:45:05.000000000 +0000 @@ -78,20 +78,20 @@ -} -import Hint.Type(ModuHint,ModuleEx(..),DeclHint',Idea(..),rawIdea',warn') +import Hint.Type(ModuHint,ModuleEx(..),DeclHint,Idea(..),rawIdea,warn) import Config.Type -import Data.Generics.Uniplate.Operations +import Data.Generics.Uniplate.DataOnly import Data.List.Extra import qualified Data.Map as Map import BasicTypes -import HsSyn +import GHC.Hs import RdrName import Outputable import Bag import SrcLoc -import GHC.Util +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable smellModuleHint :: [Setting] -> ModuHint smellModuleHint settings scope m = @@ -101,13 +101,13 @@ Just n | length imports >= n -> let span = foldl1 combineSrcSpans $ getLoc <$> imports displayImports = unlines $ f <$> imports - in [rawIdea' Config.Type.Warning "Many imports" span displayImports Nothing [] [] ] + in [rawIdea Config.Type.Warning "Many imports" span displayImports Nothing [] [] ] where f :: LImportDecl GhcPs -> String f = trimStart . unsafePrettyPrint _ -> [] -smellHint :: [Setting] -> DeclHint' +smellHint :: [Setting] -> DeclHint smellHint settings scope m d = sniff smellLongFunctions SmellLongFunctions ++ sniff smellLongTypeLists SmellLongTypeLists ++ @@ -130,10 +130,10 @@ -- right hand sides?) declSpans :: LHsDecl GhcPs -> [(SrcSpan, Idea)] declSpans - (LL _ (ValD _ + (L _ (ValD _ FunBind {fun_matches=MG { mg_origin=FromSource - , mg_alts=(LL _ [LL _ Match { + , mg_alts=(L _ [L _ Match { m_ctxt=ctx , m_grhss=GRHSs{grhssGRHSs=[locGrhs] , grhssLocalBinds=where_}}])}})) = @@ -141,20 +141,20 @@ -- the where clause. rhsSpans ctx locGrhs ++ whereSpans where_ -- Any other kind of function. -declSpans f@(LL l (ValD _ FunBind {})) = [(l, warn' "Long function" f f [])] +declSpans f@(L l (ValD _ FunBind {})) = [(l, warn "Long function" f f [])] declSpans _ = [] -- The span of a guarded right hand side. rhsSpans :: HsMatchContext RdrName -> LGRHS GhcPs (LHsExpr GhcPs) -> [(SrcSpan, Idea)] -rhsSpans _ (LL _ (GRHS _ _ (LL _ RecordCon {}))) = [] -- record constructors get a pass -rhsSpans ctx (LL _ r@(GRHS _ _ (LL l _))) = - [(l, rawIdea' Config.Type.Warning "Long function" l (showSDocUnsafe (pprGRHS ctx r)) Nothing [] [])] +rhsSpans _ (L _ (GRHS _ _ (L _ RecordCon {}))) = [] -- record constructors get a pass +rhsSpans ctx (L _ r@(GRHS _ _ (L l _))) = + [(l, rawIdea Config.Type.Warning "Long function" l (showSDocUnsafe (pprGRHS ctx r)) Nothing [] [])] rhsSpans _ _ = [] -- The spans of a 'where' clause are the spans of its bindings. whereSpans :: LHsLocalBinds GhcPs -> [(SrcSpan, Idea)] -whereSpans (LL l (HsValBinds _ (ValBinds _ bs _))) = - concatMap (declSpans . (\(LL loc bind) -> LL loc (ValD noExt bind))) (bagToList bs) +whereSpans (L l (HsValBinds _ (ValBinds _ bs _))) = + concatMap (declSpans . (\(L loc bind) -> L loc (ValD noExtField bind))) (bagToList bs) whereSpans _ = [] spanLength :: SrcSpan -> Int @@ -162,16 +162,16 @@ spanLength (UnhelpfulSpan _) = -1 smellLongTypeLists :: LHsDecl GhcPs -> Int -> [Idea] -smellLongTypeLists d@(LL _ (SigD _ (TypeSig _ _ (HsWC _ (HsIB _ (LL _ t)))))) n = - warn' "Long type list" d d [] <$ filter longTypeList (universe t) +smellLongTypeLists d@(L _ (SigD _ (TypeSig _ _ (HsWC _ (HsIB _ (L _ t)))))) n = + warn "Long type list" d d [] <$ filter longTypeList (universe t) where longTypeList (HsExplicitListTy _ IsPromoted x) = length x >= n longTypeList _ = False smellLongTypeLists _ _ = [] smellManyArgFunctions :: LHsDecl GhcPs -> Int -> [Idea] -smellManyArgFunctions d@(LL _ (SigD _ (TypeSig _ _ (HsWC _ (HsIB _ (LL _ t)))))) n = - warn' "Many arg function" d d [] <$ filter manyArgFunction (universe t) +smellManyArgFunctions d@(L _ (SigD _ (TypeSig _ _ (HsWC _ (HsIB _ (L _ t)))))) n = + warn "Many arg function" d d [] <$ filter manyArgFunction (universe t) where manyArgFunction t = countFunctionArgs t >= n smellManyArgFunctions _ _ = [] diff -Nru hlint-2.2.11/src/Hint/Type.hs hlint-3.1.6/src/Hint/Type.hs --- hlint-2.2.11/src/Hint/Type.hs 2019-11-30 14:29:43.000000000 +0000 +++ hlint-3.1.6/src/Hint/Type.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,21 +1,20 @@ module Hint.Type( - DeclHint, DeclHint', ModuHint, CrossHint, Hint(..), + DeclHint, ModuHint, CrossHint, Hint(..), module Export ) where import Data.Semigroup import Config.Type -import HSE.All as Export +import GHC.All as Export import Idea as Export import Prelude import Refact as Export -import HsExtension -import HsDecls +import GHC.Hs.Extension +import GHC.Hs.Decls import GHC.Util.Scope -type DeclHint = Scope -> ModuleEx -> Decl_ -> [Idea] -type DeclHint' = Scope' -> ModuleEx -> LHsDecl GhcPs -> [Idea] +type DeclHint = Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea] type ModuHint = Scope -> ModuleEx -> [Idea] type CrossHint = [(Scope, ModuleEx)] -> [Idea] @@ -23,19 +22,17 @@ data Hint {- PUBLIC -} = Hint { hintModules :: [Setting] -> [(Scope, ModuleEx)] -> [Idea] -- ^ Given a list of modules (and their scope information) generate some 'Idea's. , hintModule :: [Setting] -> Scope -> ModuleEx -> [Idea] -- ^ Given a single module and its scope information generate some 'Idea's. - , hintDecl :: [Setting] -> Scope -> ModuleEx -> Decl SrcSpanInfo -> [Idea] - , hintDecl' :: [Setting] -> Scope' -> ModuleEx -> LHsDecl GhcPs -> [Idea] + , hintDecl :: [Setting] -> Scope -> ModuleEx -> LHsDecl GhcPs -> [Idea] -- ^ Given a declaration (with a module and scope) generate some 'Idea's. -- This function will be partially applied with one module/scope, then used on multiple 'Decl' values. } instance Semigroup Hint where - Hint x1 x2 x3 x4 <> Hint y1 y2 y3 y4 = Hint + Hint x1 x2 x3 <> Hint y1 y2 y3 = Hint (\a b -> x1 a b ++ y1 a b) (\a b c -> x2 a b c ++ y2 a b c) (\a b c d -> x3 a b c d ++ y3 a b c d) - (\a b c d -> x4 a b c d ++ y4 a b c d) instance Monoid Hint where - mempty = Hint (\_ _ -> []) (\_ _ _ -> []) (\_ _ _ _ -> []) (\_ _ _ _ -> []) + mempty = Hint (\_ _ -> []) (\_ _ _ -> []) (\_ _ _ _ -> []) mappend = (<>) diff -Nru hlint-2.2.11/src/Hint/Unsafe.hs hlint-3.1.6/src/Hint/Unsafe.hs --- hlint-2.2.11/src/Hint/Unsafe.hs 2020-02-02 14:47:53.000000000 +0000 +++ hlint-3.1.6/src/Hint/Unsafe.hs 2020-06-14 18:45:05.000000000 +0000 @@ -18,19 +18,19 @@ module Hint.Unsafe(unsafeHint) where -import Hint.Type(DeclHint',ModuleEx(..),Severity(..),rawIdea',toSS') -import Data.Char +import Hint.Type(DeclHint,ModuleEx(..),Severity(..),rawIdea,toSS) +import Data.List.Extra import Refact.Types hiding(Match) -import Data.Generics.Uniplate.Operations +import Data.Generics.Uniplate.DataOnly -import HsSyn +import GHC.Hs import OccName import RdrName import FastString import BasicTypes import SrcLoc import Language.Haskell.GhclibParserEx.GHC.Hs.Expr -import GHC.Util +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable -- The conditions on which to fire this hint are subtle. We are -- interested exclusively in application constants involving @@ -44,12 +44,12 @@ -- f = g where g = unsafePerformIO Multimap.newIO -- @ -- is. We advise that such constants should have a @NOINLINE@ pragma. -unsafeHint :: DeclHint' -unsafeHint _ (ModuleEx _ _ (L _ m) _) = \(L loc d) -> - [rawIdea' Hint.Type.Warning "Missing NOINLINE pragma" loc +unsafeHint :: DeclHint +unsafeHint _ (ModuleEx (L _ m) _) = \(L loc d) -> + [rawIdea Hint.Type.Warning "Missing NOINLINE pragma" loc (unsafePrettyPrint d) - (Just $ dropWhile isSpace (unsafePrettyPrint $ gen x) ++ "\n" ++ unsafePrettyPrint d) - [] [InsertComment (toSS' (L loc d)) (unsafePrettyPrint $ gen x)] + (Just $ trimStart (unsafePrettyPrint $ gen x) ++ "\n" ++ unsafePrettyPrint d) + [] [InsertComment (toSS (L loc d)) (unsafePrettyPrint $ gen x)] -- 'x' does not declare a new function. | d@(ValD _ FunBind {fun_id=L _ (Unqual x) @@ -61,26 +61,26 @@ where gen :: OccName -> LHsDecl GhcPs gen x = noLoc $ - SigD noExt (InlineSig noExt (noLoc (mkRdrUnqual x)) + SigD noExtField (InlineSig noExtField (noLoc (mkRdrUnqual x)) (InlinePragma (SourceText "{-# NOINLINE") NoInline Nothing NeverActive FunLike)) noinline :: [OccName] - noinline = [q | LL _(SigD _ (InlineSig _ (L _ (Unqual q)) + noinline = [q | L _(SigD _ (InlineSig _ (L _ (Unqual q)) (InlinePragma _ NoInline Nothing NeverActive FunLike)) ) <- hsmodDecls m] isUnsafeDecl :: HsDecl GhcPs -> Bool -isUnsafeDecl (ValD _ FunBind {fun_matches=MG {mg_origin=FromSource,mg_alts=LL _ alts}}) = +isUnsafeDecl (ValD _ FunBind {fun_matches=MG {mg_origin=FromSource,mg_alts=L _ alts}}) = any isUnsafeApp (childrenBi alts) || any isUnsafeDecl (childrenBi alts) isUnsafeDecl _ = False -- Am I equivalent to @unsafePerformIO x@? isUnsafeApp :: HsExpr GhcPs -> Bool -isUnsafeApp (OpApp _ (LL _ l) op _ ) | isDol op = isUnsafeFun l -isUnsafeApp (HsApp _ (LL _ x) _) = isUnsafeFun x +isUnsafeApp (OpApp _ (L _ l) op _ ) | isDol op = isUnsafeFun l +isUnsafeApp (HsApp _ (L _ x) _) = isUnsafeFun x isUnsafeApp _ = False -- Am I equivalent to @unsafePerformIO . x@? isUnsafeFun :: HsExpr GhcPs -> Bool -isUnsafeFun (HsVar _ (LL _ x)) | x == mkVarUnqual (fsLit "unsafePerformIO") = True -isUnsafeFun (OpApp _ (LL _ l) op _) | isDot op = isUnsafeFun l +isUnsafeFun (HsVar _ (L _ x)) | x == mkVarUnqual (fsLit "unsafePerformIO") = True +isUnsafeFun (OpApp _ (L _ l) op _) | isDot op = isUnsafeFun l isUnsafeFun _ = False diff -Nru hlint-2.2.11/src/Hint/Util.hs hlint-3.1.6/src/Hint/Util.hs --- hlint-2.2.11/src/Hint/Util.hs 2019-08-27 20:12:01.000000000 +0000 +++ hlint-3.1.6/src/Hint/Util.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,95 +0,0 @@ -{-# LANGUAGE PatternGuards, ViewPatterns #-} - -module Hint.Util(niceLambdaR) where - -import HSE.All -import Data.List.Extra -import Refact.Types -import Refact -import qualified Refact.Types as R (SrcSpan) - --- | Generate a lambda, but prettier (if possible). --- Generally no lambda is good, but removing just some arguments isn't so useful. -niceLambdaR :: [String] -> Exp_ -> (Exp_, R.SrcSpan -> [Refactoring R.SrcSpan]) - --- \xs -> (e) ==> \xs -> e -niceLambdaR xs (Paren l x) = niceLambdaR xs x - --- \xs -> \v vs -> e ==> \xs v -> \vs -> e --- \xs -> \ -> e ==> \xs -> e -niceLambdaR xs (Lambda _ ((view -> PVar_ v):vs) x) | v `notElem` xs = niceLambdaR (xs++[v]) (Lambda an vs x) -niceLambdaR xs (Lambda _ [] x) = niceLambdaR xs x - --- \ -> e ==> e -niceLambdaR [] x = (x, const []) - --- \vs v -> e $ v ==> \vs -> e -niceLambdaR (unsnoc -> Just (vs, v)) (InfixApp _ e (isDol -> True) (view -> Var_ v2)) - | v == v2, vars e `disjoint` [v] - = niceLambdaR vs e - --- \xs -> e xs ==> e -niceLambdaR xs (fromAppsWithLoc -> e) | map view xs2 == map Var_ xs, vars e2 `disjoint` xs, not $ null e2 = - (apps e2, \s -> [Replace Expr s [("x", pos)] "x"]) - where (e',xs') = splitAt (length e - length xs) e - (e2, xs2) = (map fst e', map fst xs') - pos = toRefactSrcSpan . srcInfoSpan $ snd (last e') - --- \x y -> x + y ==> (+) -niceLambdaR [x,y] (InfixApp _ (view -> Var_ x1) (opExp -> op) (view -> Var_ y1)) - | x == x1, y == y1, vars op `disjoint` [x,y] = (op, \s -> [Replace Expr s [] (prettyPrint op)]) - --- \x -> x + a ==> (+ a) [heuristic, ab must be a single lexeme, or gets too complex] -niceLambdaR [x] (view -> App2 (expOp -> Just op) xx a) - | isLexeme a, view xx == Var_ x, x `notElem` vars a, allowRightSection (fromNamed op) = - let e = rebracket1 $ RightSection an op a - in (e, \s -> [Replace Expr s [] (prettyPrint e)]) - --- \x -> a + x ==> (a +) [heuristic, a must be a single lexeme, or gets too complex] -niceLambdaR [x] (view -> App2 (expOp -> Just op) a xx) - | isLexeme a, view xx == Var_ x, x `notElem` vars a = - let e = rebracket1 $ LeftSection an a op - in (e, \s -> [Replace Expr s [] (prettyPrint e)]) - --- \x y -> f y x = flip f -niceLambdaR [x,y] (view -> App2 op (view -> Var_ y1) (view -> Var_ x1)) - | x == x1, y == y1, vars op `disjoint` [x,y] = (gen op, \s -> [Replace Expr s [("x", toSS op)] (prettyPrint $ gen (toNamed "x"))]) - where - gen = App an (toNamed "flip") - --- \x -> f (b x) ==> f . b --- \x -> f $ b x ==> f . b -niceLambdaR [x] y | Just (z, subts) <- factor y, x `notElem` vars z = (z, \s -> [mkRefact subts s]) - where - -- factor the expression with respect to x - factor y@(App _ ini lst) | view lst == Var_ x = Just (ini, [ann ini]) - factor y@(App _ ini lst) | Just (z, ss) <- factor lst = let r = niceDotApp ini z - in if r == z then Just (r, ss) - else Just (r, ann ini : ss) - factor (InfixApp _ y op (factor -> Just (z, ss))) | isDol op = let r = niceDotApp y z - in if r == z then Just (r, ss) - else Just (r, ann y : ss) - factor (Paren _ y@App{}) = factor y - factor _ = Nothing - mkRefact :: [S] -> R.SrcSpan -> Refactoring R.SrcSpan - mkRefact subts s = - let tempSubts = zipWith (\a b -> ([a], toRefactSrcSpan $ srcInfoSpan b)) ['a' .. 'z'] subts - template = dotApps (map (toNamed . fst) tempSubts) - in Replace Expr s tempSubts (prettyPrint template) - - --- \x -> (x +) ==> (+) --- Section handling is not yet supported for refactoring -niceLambdaR [x] (LeftSection _ (view -> Var_ x1) op) | x == x1 = - let e = opExp op - in (e, \s -> [Replace Expr s [] (prettyPrint e)]) - --- base case -niceLambdaR ps x = (Lambda an (map toNamed ps) x, const []) - - - --- ($) . b ==> b -niceDotApp :: Exp_ -> Exp_ -> Exp_ -niceDotApp a b | a ~= "$" = b - | otherwise = dotApp a b diff -Nru hlint-2.2.11/src/HLint.hs hlint-3.1.6/src/HLint.hs --- hlint-2.2.11/src/HLint.hs 2020-01-21 14:43:12.000000000 +0000 +++ hlint-3.1.6/src/HLint.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,13 +1,15 @@ +{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-} -{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-} +{-# OPTIONS_GHC -Wno-incomplete-patterns #-} module HLint(hlint, readAllSettings) where import Control.Applicative import Control.Monad.Extra -import Control.Exception +import Control.Exception.Extra import Control.Concurrent.Extra import System.Console.CmdArgs.Verbosity +import GHC.Util.DynFlags import Data.List.Extra import GHC.Conc import System.Exit @@ -16,11 +18,6 @@ import Data.Tuple.Extra import Prelude -import Data.Version.Extra -import System.Process.Extra -import Data.Maybe -import System.Directory - import CmdLine import Config.Read import Config.Type @@ -31,10 +28,11 @@ import Test.All import Hint.All import Grep +import Refact import Timing import Test.Proof import Parallel -import HSE.All +import GHC.All import CC import EmbedData @@ -53,6 +51,7 @@ -- on your server with untrusted input. hlint :: [String] -> IO [Idea] hlint args = do + initGlobalDynFlags cmd <- getCmd args case cmd of CmdMain{} -> do @@ -61,27 +60,9 @@ when (cmdTiming cmd) $ do printTimings putStrLn $ "Took " ++ showDuration time - return $ if cmdNoExitCode cmd then [] else xs - CmdGrep{} -> hlintGrep cmd >> return [] - CmdHSE{} -> hlintHSE cmd >> return [] - CmdTest{} -> hlintTest cmd >> return [] - -hlintHSE :: Cmd -> IO () -hlintHSE c@CmdHSE{..} = do - v <- getVerbosity - forM_ cmdFiles $ \x -> do - putStrLn $ "Parse result of " ++ x ++ ":" - let (lang,exts) = cmdExtensions c - -- We deliberately don't use HSE.All here to avoid any bugs in HLint - -- polluting our bug reports (which is the main use of HSE) - res <- parseFileWithMode defaultParseMode{baseLanguage=lang, extensions=exts} x - case res of - x@ParseFailed{} -> print x - ParseOk m -> case v of - Loud -> print m - Quiet -> print $ prettyPrint m - _ -> print $ void m - putStrLn "" + pure $ if cmdNoExitCode cmd then [] else xs + CmdGrep{} -> hlintGrep cmd >> pure [] + CmdTest{} -> hlintTest cmd >> pure [] hlintTest :: Cmd -> IO () hlintTest cmd@CmdTest{..} = @@ -104,7 +85,7 @@ else do files <- concatMapM (resolveFile cmd Nothing) cmdFiles if null files then - error "No files found" + errorIO "No files found" else runGrep cmdPattern (cmdParseFlags cmd) files @@ -116,7 +97,7 @@ hlintMain :: [String] -> Cmd -> IO [Idea] hlintMain args cmd@CmdMain{..} | cmdDefault = do - ideas <- if null cmdFiles then return [] else withVerbosity Quiet $ + ideas <- if null cmdFiles then pure [] else withVerbosity Quiet $ runHlintMain args cmd{cmdJson=False,cmdSerialise=False,cmdRefactor=False} Nothing let bad = nubOrd $ map ideaHint ideas if null bad then putStr defaultYaml else do @@ -124,10 +105,10 @@ let group2 = "# Warnings currently triggered by your code" : ["- ignore: {name: " ++ show x ++ "}" | x <- bad] putStr $ unlines $ intercalate ["",""] $ group1:group2:groups - return [] + pure [] | null cmdFiles && not (null cmdFindHints) = do hints <- concatMapM (resolveFile cmd Nothing) cmdFindHints - mapM_ (putStrLn . fst <=< computeSettings (cmdParseFlags cmd)) hints >> return [] + mapM_ (putStrLn . fst <=< computeSettings (cmdParseFlags cmd)) hints >> pure [] | null cmdFiles = exitWithHelp | cmdRefactor = @@ -154,13 +135,12 @@ settings1 <- readFilesConfig $ files - ++ [("CommandLine.hs",Just x) | x <- cmdWithHints] ++ [("CommandLine.yaml",Just (enableGroup x)) | x <- cmdWithGroups] let args2 = [x | SettingArgument x <- settings1] - cmd@CmdMain{..} <- if null args2 then return cmd else getCmd $ args2 ++ args1 -- command line arguments are passed last + cmd@CmdMain{..} <- if null args2 then pure cmd else getCmd $ args2 ++ args1 -- command line arguments are passed last settings2 <- concatMapM (fmap snd . computeSettings (cmdParseFlags cmd)) cmdFindHints - settings3 <- return [SettingClassify $ Classify Ignore x "" "" | x <- cmdIgnore] - return (cmd, settings1 ++ settings2 ++ settings3) + let settings3 = [SettingClassify $ Classify Ignore x "" "" | x <- cmdIgnore] + pure (cmd, settings1 ++ settings2 ++ settings3) where enableGroup groupName = unlines @@ -171,11 +151,11 @@ runHints :: [String] -> [Setting] -> Cmd -> IO [Idea] runHints args settings cmd@CmdMain{..} = do - j <- if cmdThreads == 0 then getNumProcessors else return cmdThreads + j <- if cmdThreads == 0 then getNumProcessors else pure cmdThreads withNumCapabilities j $ do let outStrLn = whenNormal . putStrLn ideas <- getIdeas cmd settings - ideas <- return $ if cmdShowAll then ideas else filter (\i -> ideaSeverity i /= Ignore) ideas + ideas <- pure $ if cmdShowAll then ideas else filter (\i -> ideaSeverity i /= Ignore) ideas if cmdJson then putStrLn $ showIdeasJson ideas else if cmdCC then @@ -187,24 +167,25 @@ handleRefactoring ideas cmdFiles cmd else do usecolour <- cmdUseColour cmd - showItem <- if usecolour then showANSI else return show + showItem <- if usecolour then showANSI else pure show mapM_ (outStrLn . showItem) ideas handleReporting ideas cmd - return ideas + pure ideas getIdeas :: Cmd -> [Setting] -> IO [Idea] getIdeas cmd@CmdMain{..} settings = do - settings <- return $ settings ++ map (Builtin . fst) builtinHints + settings <- pure $ settings ++ map (Builtin . fst) builtinHints let flags = cmdParseFlags cmd ideas <- if cmdCross then applyHintFiles flags settings cmdFiles else concat <$> parallel cmdThreads [evaluateList =<< applyHintFile flags settings x Nothing | x <- cmdFiles] - return $ if not (null cmdOnly) + pure $ if not (null cmdOnly) then [i | i <- ideas, ideaHint i `elem` cmdOnly] else ideas +-- #746: run refactor even if no hint, which ensures consistent output +-- whether there are hints or not. handleRefactoring :: [Idea] -> [String] -> Cmd -> IO () -handleRefactoring [] _ _ = pure () -- No refactorings to apply handleRefactoring ideas files cmd@CmdMain{..} = case cmdFiles of [file] -> do @@ -214,9 +195,9 @@ let hints = show $ map (show &&& ideaRefactoring) ideas withTempFile $ \f -> do writeFile f hints - exitWith =<< runRefactoring path file f cmdRefactorOptions - _ -> error "Refactor flag can only be used with an individual file" - + let ParseFlags{enabledExtensions, disabledExtensions} = cmdParseFlags cmd + exitWith =<< runRefactoring path file f enabledExtensions disabledExtensions cmdRefactorOptions + _ -> errorIO "Refactor flag can only be used with an individual file" handleReporting :: [Idea] -> Cmd -> IO () handleReporting showideas cmd@CmdMain{..} = do @@ -228,28 +209,7 @@ let n = length showideas outStrLn $ if n == 0 then "No hints" else show n ++ " hint" ++ ['s' | n/=1] -runRefactoring :: FilePath -> FilePath -> FilePath -> String -> IO ExitCode -runRefactoring rpath fin hints opts = do - let args = [fin, "-v0"] ++ words opts ++ ["--refact-file", hints] - (_, _, _, phand) <- createProcess $ proc rpath args - try $ hSetBuffering stdin LineBuffering :: IO (Either IOException ()) - hSetBuffering stdout LineBuffering - -- Propagate the exit code from the spawn process - waitForProcess phand - -checkRefactor :: Maybe FilePath -> IO FilePath -checkRefactor rpath = do - let excPath = fromMaybe "refactor" rpath - mexc <- findExecutable excPath - case mexc of - Just exc -> do - ver <- readVersion . tail <$> readProcess exc ["--version"] "" - if versionBranch ver >= [0,1,0,0] - then return exc - else error "Your version of refactor is too old, please upgrade to the latest version" - Nothing -> error $ unlines [ "Could not find refactor", "Tried with: " ++ excPath ] - evaluateList :: [a] -> IO [a] evaluateList xs = do evaluate $ length xs - return xs + pure xs diff -Nru hlint-2.2.11/src/HsColour.hs hlint-3.1.6/src/HsColour.hs --- hlint-2.2.11/src/HsColour.hs 2018-01-10 17:17:42.000000000 +0000 +++ hlint-3.1.6/src/HsColour.hs 2020-03-01 21:35:43.000000000 +0000 @@ -4,7 +4,7 @@ #ifdef GPL_SCARES_ME hsColourConsole :: IO (String -> String) -hsColourConsole = return id +hsColourConsole = pure id hsColourHTML :: String -> String hsColourHTML = id diff -Nru hlint-2.2.11/src/HSE/All.hs hlint-3.1.6/src/HSE/All.hs --- hlint-2.2.11/src/HSE/All.hs 2020-01-21 14:43:12.000000000 +0000 +++ hlint-3.1.6/src/HSE/All.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,423 +0,0 @@ -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE TupleSections #-} - -module HSE.All( - module X, - CppFlags(..), ParseFlags(..), defaultParseFlags, - parseFlagsAddFixities, parseFlagsSetLanguage, - ParseError(..), ModuleEx(..), - parseModuleEx, ghcComments, - freeVars, vars, varss, pvars, - ghcSpanToHSE, ghcSrcLocToHSE, - parseExpGhcWithMode, parseImportDeclGhcWithMode - ) where - -import Language.Haskell.Exts.Util hiding (freeVars, Vars(..)) -import qualified Language.Haskell.Exts.Util as X -import HSE.Util as X -import HSE.Type as X -import HSE.Match as X -import HSE.Scope as X -import Util -import Data.Char -import Data.List.Extra -import Data.Maybe -import Timing -import Language.Preprocessor.Cpphs -import Data.Either -import Data.Set (Set) -import qualified Data.Map as Map -import qualified Data.Set as Set -import System.IO.Extra -import Data.Functor -import Prelude - -import qualified HsSyn -import qualified FastString -import qualified SrcLoc as GHC -import qualified ErrUtils -import qualified Outputable -import qualified Lexer as GHC -import qualified GHC.LanguageExtensions.Type as GHC -import qualified ApiAnnotation as GHC -import qualified BasicTypes as GHC -import qualified DynFlags as GHC - -import GHC.Util -import qualified Language.Haskell.GhclibParserEx.Fixity as GhclibParserEx - --- | Convert a GHC source loc into an HSE equivalent. -ghcSrcLocToHSE :: GHC.SrcLoc -> SrcLoc -ghcSrcLocToHSE (GHC.RealSrcLoc l) = - SrcLoc { - srcFilename = FastString.unpackFS (GHC.srcLocFile l) - , srcLine = GHC.srcLocLine l - , srcColumn = GHC.srcLocCol l - } -ghcSrcLocToHSE (GHC.UnhelpfulLoc _) = noLoc - --- | Convert a GHC source span into an HSE equivalent. -ghcSpanToHSE :: GHC.SrcSpan -> SrcSpan -ghcSpanToHSE (GHC.RealSrcSpan s) = - SrcSpan { - srcSpanFilename = FastString.unpackFS (GHC.srcSpanFile s) - , srcSpanStartLine = GHC.srcSpanStartLine s - , srcSpanStartColumn = GHC.srcSpanStartCol s - , srcSpanEndLine = GHC.srcSpanEndLine s - , srcSpanEndColumn = GHC.srcSpanEndCol s - } -ghcSpanToHSE (GHC.UnhelpfulSpan _) = mkSrcSpan noLoc noLoc - -vars :: FreeVars a => a -> [String] -freeVars :: FreeVars a => a -> Set String -varss, pvars :: AllVars a => a -> [String] -vars = Set.toList . Set.map prettyPrint . X.freeVars -varss = Set.toList . Set.map prettyPrint . X.free . X.allVars -pvars = Set.toList . Set.map prettyPrint . X.bound . X.allVars -freeVars = Set.map prettyPrint . X.freeVars - --- | What C pre processor should be used. -data CppFlags - = NoCpp -- ^ No pre processing is done. - | CppSimple -- ^ Lines prefixed with @#@ are stripped. - | Cpphs CpphsOptions -- ^ The @cpphs@ library is used. - --- | Created with 'defaultParseFlags', used by 'parseModuleEx'. -data ParseFlags = ParseFlags - {cppFlags :: CppFlags -- ^ How the file is preprocessed (defaults to 'NoCpp'). - ,hseFlags :: ParseMode -- ^ How the file is parsed (defaults to all fixities in the @base@ package and most non-conflicting extensions). - } - -lensFixities :: [Fixity] -lensFixities = concat - -- List as provided at https://github.com/ndmitchell/hlint/issues/416 - [infixr_ 4 ["%%@~","<%@~","%%~","<+~","<*~","<-~","","??"] - ,infixl_ 8 ["^.","^@."] - ,infixr_ 9 ["<.>","<.",".>"] - ,infixr_ 4 ["%@~",".~","+~","*~","-~","//~","^~","^^~","**~","&&~","<>~","||~","%~"] - ,infix_ 4 ["%@=",".=","+=","*=","-=","//=","^=","^^=","**=","&&=","<>=","||=","%="] - ,infixr_ 2 ["<~"] - ,infixr_ 2 ["`zoom`","`magnify`"] - ,infixl_ 8 ["^..","^?","^?!","^@..","^@?","^@?!"] - ,infixl_ 8 ["^#"] - ,infixr_ 4 ["<#~","#~","#%~","<#%~","#%%~"] - ,infix_ 4 ["<#=","#=","#%=","<#%=","#%%="] - ,infixl_ 9 [":>"] - ,infixr_ 4 ["~","<~","<.>~","<<.>~"] - ,infix_ 4 ["=","<=","<.>=","<<.>="] - ,infixr_ 4 [".|.~",".&.~","<.|.~","<.&.~"] - ,infix_ 4 [".|.=",".&.=","<.|.=","<.&.="] - ] - -otherFixities :: [Fixity] -otherFixities = concat - -- hspec - [infix_ 1 ["`shouldBe`","`shouldSatisfy`","`shouldStartWith`","`shouldEndWith`","`shouldContain`","`shouldMatchList`" - ,"`shouldReturn`","`shouldNotBe`","`shouldNotSatisfy`","`shouldNotContain`","`shouldNotReturn`","`shouldThrow`"] - -- quickcheck - ,infixr_ 0 ["==>"] - ,infix_ 4 ["==="] - -- esqueleto - ,infix_ 4 ["==."] - -- lattices - ,infixr_ 5 ["\\/"] -- \/ - ,infixr_ 6 ["/\\"] -- /\ - ] - --- Fixites from the `base` package which are currently --- missing from `haskell-src-exts`'s baseFixities. --- see https://github.com/haskell-suite/haskell-src-exts/pull/400 -baseNotYetInHSE :: [Fixity] -baseNotYetInHSE = concat - [infixr_ 9 ["`Compose`"] - ,infixr_ 6 ["<>"] - ,infixr_ 5 ["<|"] - ,infixl_ 4 ["<$!>","<$","$>"] - ,infix_ 4 [":~:", ":~~:"] - ] - -customFixities :: [Fixity] -customFixities = - infixl_ 1 ["`on`"] - -- see https://github.com/ndmitchell/hlint/issues/425 - -- otherwise GTK apps using `on` at a different fixity have spurious warnings - --- | Default value for 'ParseFlags'. -defaultParseFlags :: ParseFlags -defaultParseFlags = ParseFlags NoCpp defaultParseMode - {fixities = Just $ customFixities ++ baseFixities ++ baseNotYetInHSE ++ lensFixities ++ otherFixities - ,ignoreLinePragmas = False - ,ignoreFunctionArity = True - ,extensions = parseExtensions} - -parseFlagsNoLocations :: ParseFlags -> ParseFlags -parseFlagsNoLocations x = x{cppFlags = case cppFlags x of Cpphs y -> Cpphs $ f y; y -> y} - where f x = x{boolopts = (boolopts x){locations=False}} - --- | Given some fixities, add them to the existing fixities in 'ParseFlags'. -parseFlagsAddFixities :: [Fixity] -> ParseFlags -> ParseFlags -parseFlagsAddFixities fx x = x{hseFlags=hse{fixities = Just $ fx ++ fromMaybe [] (fixities hse)}} - where hse = hseFlags x - -parseFlagsSetLanguage :: (Language, [Extension]) -> ParseFlags -> ParseFlags -parseFlagsSetLanguage (l, es) x = x{hseFlags=(hseFlags x){baseLanguage = l, extensions = es}} - - -runCpp :: CppFlags -> FilePath -> String -> IO String -runCpp NoCpp _ x = return x -runCpp CppSimple _ x = return $ unlines [if "#" `isPrefixOf` trimStart x then "" else x | x <- lines x] -runCpp (Cpphs o) file x = dropLine <$> runCpphs o file x - where - -- LINE pragmas always inserted when locations=True - dropLine (line1 -> (a,b)) | "{-# LINE " `isPrefixOf` a = b - dropLine x = x - ---------------------------------------------------------------------- --- PARSING - --- | A parse error. -data ParseError = ParseError - { parseErrorLocation :: SrcLoc -- ^ Location of the error. - , parseErrorMessage :: String -- ^ Message about the cause of the error. - , parseErrorContents :: String -- ^ Snippet of several lines (typically 5) including a @>@ character pointing at the faulty line. - } - --- | Result of 'parseModuleEx', representing a parsed module. -data ModuleEx = ModuleEx { - hseModule :: Module SrcSpanInfo - , hseComments :: [Comment] - , ghcModule :: GHC.Located (HsSyn.HsModule HsSyn.GhcPs) - , ghcAnnotations :: GHC.ApiAnns -} - --- | Extract a list of all of a parsed module's comments. -ghcComments :: ModuleEx -> [GHC.Located GHC.AnnotationComment] -ghcComments m = concat (Map.elems $ snd (ghcAnnotations m)) - --- | Utility called from 'parseModuleEx' and 'hseFailOpParseModuleEx'. -mkMode :: ParseFlags -> String -> ParseMode -mkMode flags file = (hseFlags flags){ parseFilename = file,fixities = Nothing } - --- | Error handler dispatcher. Invoked when HSE parsing has failed. -failOpParseModuleEx :: String - -> ParseFlags - -> FilePath - -> String - -> SrcLoc - -> String - -> Maybe (GHC.SrcSpan, ErrUtils.MsgDoc) - -> IO (Either ParseError ModuleEx) -failOpParseModuleEx ppstr flags file str sl msg ghc = - case ghc of - Just err -> - -- GHC error info is available (assumed to have come from a - -- 'PFailed'). We prefer to construct a 'ParseError' value - -- using that. - ghcFailOpParseModuleEx ppstr file str err - Nothing -> - -- No GHC error info provided. This is the traditional approach - -- to handling errors. - hseFailOpParseModuleEx ppstr flags file str sl msg - --- | An error handler of last resort. This is invoked when HSE parsing --- has failed but apparently GHC has not! -hseFailOpParseModuleEx :: String - -> ParseFlags - -> FilePath - -> String - -> SrcLoc - -> String - -> IO (Either ParseError ModuleEx) -hseFailOpParseModuleEx ppstr flags file str sl msg = do - flags <- return $ parseFlagsNoLocations flags - ppstr2 <- runCpp (cppFlags flags) file str - let pe = case parseFileContentsWithMode (mkMode flags file) ppstr2 of - ParseFailed sl2 _ -> context (srcLine sl2) ppstr2 - _ -> context (srcLine sl) ppstr - return $ Left $ ParseError sl msg pe - --- | The error handler invoked when GHC parsing has failed. -ghcFailOpParseModuleEx :: String - -> FilePath - -> String - -> (GHC.SrcSpan, ErrUtils.MsgDoc) - -> IO (Either ParseError ModuleEx) -ghcFailOpParseModuleEx ppstr file str (loc, err) = do - let sl = - case loc of - GHC.RealSrcSpan r -> - SrcLoc { srcFilename = FastString.unpackFS (GHC.srcSpanFile r) - , srcLine = GHC.srcSpanStartLine r - , srcColumn = GHC.srcSpanStartCol r } - GHC.UnhelpfulSpan _ -> - SrcLoc { srcFilename = file - , srcLine = 1 :: Int - , srcColumn = 1 :: Int } - pe = context (srcLine sl) ppstr - msg = Outputable.showSDoc baseDynFlags $ - ErrUtils.pprLocErrMsg (ErrUtils.mkPlainErrMsg baseDynFlags loc err) - return $ Left $ ParseError sl msg pe - --- A hacky function to get fixities from HSE parse flags suitable for --- use by our own 'GHC.Util.Refact.Fixity' module. -ghcFixitiesFromParseMode :: ParseMode -> [(String, GHC.Fixity)] -ghcFixitiesFromParseMode ParseMode {fixities=Just fixities} = - concatMap convert fixities - where - convert (Fixity (AssocNone _) fix name) = infix_' fix [qNameToStr name] - convert (Fixity (AssocLeft _) fix name) = infixl_' fix [qNameToStr name] - convert (Fixity (AssocRight _) fix name) = infixr_' fix [qNameToStr name] - - infixr_', infixl_', infix_' :: Int -> [String] -> [(String,GHC.Fixity)] - infixr_' = fixity' GHC.InfixR - infixl_' = fixity' GHC.InfixL - infix_' = fixity' GHC.InfixN - - fixity' :: GHC.FixityDirection -> Int -> [String] -> [(String, GHC.Fixity)] - fixity' a p = map (,GHC.Fixity (GHC.SourceText "") p a) - - qNameToStr :: QName () -> String - qNameToStr (Special _ Cons{}) = ":" - qNameToStr (Special _ UnitCon{}) = "()" - qNameToStr (UnQual _ (X.Ident _ x)) = x - qNameToStr (UnQual _ (Symbol _ x)) = x - qNameToStr _ = "" -ghcFixitiesFromParseMode _ = [] - --- GHC enabled/disabled extensions given an HSE parse mode. -ghcExtensionsFromParseMode :: ParseMode - -> ([GHC.Extension], [GHC.Extension]) -ghcExtensionsFromParseMode ParseMode {extensions=exts}= - partitionEithers $ mapMaybe toEither exts - where - toEither ke = case ke of - EnableExtension e -> Left <$> readExtension (show e) - DisableExtension e -> Right <$> readExtension (show e) - UnknownExtension ('N':'o':e) -> Right <$> readExtension e - UnknownExtension e -> Left <$> readExtension e - --- GHC extensions to enable/disable given HSE parse flags. -ghcExtensionsFromParseFlags :: ParseFlags - -> ([GHC.Extension], [GHC.Extension]) -ghcExtensionsFromParseFlags ParseFlags {hseFlags=mode} = ghcExtensionsFromParseMode mode - --- GHC fixities given HSE parse flags. -ghcFixitiesFromParseFlags :: ParseFlags -> [(String, GHC.Fixity)] -ghcFixitiesFromParseFlags ParseFlags {hseFlags=mode} = ghcFixitiesFromParseMode mode - --- These next two functions get called frorm 'Config/Yaml.hs' for user --- defined hint rules. - -parseExpGhcWithMode :: ParseMode -> String -> GHC.ParseResult (HsSyn.LHsExpr HsSyn.GhcPs) -parseExpGhcWithMode parseMode s = - let (enable, disable) = ghcExtensionsFromParseMode parseMode - flags = foldl' GHC.xopt_unset (foldl' GHC.xopt_set baseDynFlags enable) disable - fixities = ghcFixitiesFromParseMode parseMode - in case parseExpGhcLib s flags of - GHC.POk pst a -> GHC.POk pst (GhclibParserEx.applyFixities fixities a) - f@GHC.PFailed{} -> f - -parseImportDeclGhcWithMode :: ParseMode -> String -> GHC.ParseResult (HsSyn.LImportDecl HsSyn.GhcPs) -parseImportDeclGhcWithMode parseMode s = - let (enable, disable) = ghcExtensionsFromParseMode parseMode - flags = foldl' GHC.xopt_unset (foldl' GHC.xopt_set baseDynFlags enable) disable - in parseImportGhcLib s flags - --- | Parse a Haskell module. Applies the C pre processor, and uses --- best-guess fixity resolution if there are ambiguities. The --- filename @-@ is treated as @stdin@. Requires some flags (often --- 'defaultParseFlags'), the filename, and optionally the contents of --- that file. This version uses both hs-src-exts AND ghc-lib. -parseModuleEx :: ParseFlags -> FilePath -> Maybe String -> IO (Either ParseError ModuleEx) -parseModuleEx flags file str = timedIO "Parse" file $ do - str <- case str of - Just x -> return x - Nothing | file == "-" -> getContentsUTF8 - | otherwise -> readFileUTF8' file - str <- return $ fromMaybe str $ stripPrefix "\65279" str -- remove the BOM if it exists, see #130 - ppstr <- runCpp (cppFlags flags) file str - let enableDisableExts = ghcExtensionsFromParseFlags flags - fixities = ghcFixitiesFromParseFlags flags -- Note : Fixities are coming from HSE parse flags. - dynFlags <- parsePragmasIntoDynFlags baseDynFlags enableDisableExts file ppstr - case dynFlags of - Right ghcFlags -> - case (parseFileContentsWithComments (mkMode flags file) ppstr, parseFileGhcLib file ppstr ghcFlags) of - (ParseOk (x, cs), GHC.POk pst a) -> - let anns = - ( Map.fromListWith (++) $ GHC.annotations pst - , Map.fromList ((GHC.noSrcSpan, GHC.comment_q pst) : GHC.annotations_comments pst) - ) in - let a' = GhclibParserEx.applyFixities fixities a in - return $ Right (ModuleEx (applyFixity fixity x) cs a' anns) - -- Parse error if GHC parsing fails (see - -- https://github.com/ndmitchell/hlint/issues/645). - (ParseOk _, GHC.PFailed _ loc err) -> - ghcFailOpParseModuleEx ppstr file str (loc, err) - (ParseFailed sl msg, pfailed) -> - failOpParseModuleEx ppstr flags file str sl msg $ fromPFailed pfailed - Left msg -> do - -- Parsing GHC flags from dynamic pragmas in the source - -- has failed. When this happens, it's reported by - -- exception. It's impossible or at least fiddly getting a - -- location so we skip that for now. Synthesize a parse - -- error. - let loc = SrcLoc file (1 :: Int) (1 :: Int) - return $ Left (ParseError loc msg (context (srcLine loc) ppstr)) - - where - fromPFailed (GHC.PFailed _ loc err) = Just (loc, err) - fromPFailed _ = Nothing - - fixity = fromMaybe [] $ fixities $ hseFlags flags - --- | Given a line number, and some source code, put bird ticks around the appropriate bit. -context :: Int -> String -> String -context lineNo src = - unlines $ dropWhileEnd (all isSpace) $ dropWhile (all isSpace) $ - zipWith (++) ticks $ take 5 $ drop (lineNo - 3) $ lines src ++ ["","","","",""] - where ticks = [" "," ","> "," "," "] - - ---------------------------------------------------------------------- --- FIXITIES - --- resolve fixities later, so we don't ever get uncatchable ambiguity errors --- if there are fixity errors, try the cheapFixities (which never fails) -applyFixity :: [Fixity] -> Module_ -> Module_ -applyFixity base modu = descendBi f modu - where - f x = fromMaybe (cheapFixities fixs x) $ applyFixities fixs x :: Decl_ - fixs = concatMap getFixity (moduleDecls modu) ++ base - - --- Apply fixities, but ignoring any ambiguous fixity errors and skipping qualified names, --- local infix declarations etc. Only use as a backup, if HSE gives an error. --- --- Inspired by the code at: --- http://hackage.haskell.org/trac/haskell-prime/attachment/wiki/FixityResolution/resolve.hs -cheapFixities :: [Fixity] -> Decl_ -> Decl_ -cheapFixities fixs = descendBi (transform f) - where - ask = askFixity fixs - - f o@(InfixApp s1 (InfixApp s2 x op1 y) op2 z) - | p1 == p2 && (a1 /= a2 || isAssocNone a1) = o -- Ambiguous infix expression! - | p1 > p2 || p1 == p2 && (isAssocLeft a1 || isAssocNone a2) = o - | otherwise = InfixApp s1 x op1 (f $ InfixApp s1 y op2 z) - where - (a1,p1) = ask op1 - (a2,p2) = ask op2 - f x = x - - -askFixity :: [Fixity] -> QOp S -> (Assoc (), Int) -askFixity xs = \k -> Map.findWithDefault (AssocLeft (), 9) (fromNamed k) mp - where - mp = Map.fromList [(s,(a,p)) | Fixity a p x <- xs, let s = fromNamed $ fmap (const an) x, s /= ""] diff -Nru hlint-2.2.11/src/HSE/Match.hs hlint-3.1.6/src/HSE/Match.hs --- hlint-2.2.11/src/HSE/Match.hs 2019-11-02 16:50:13.000000000 +0000 +++ hlint-3.1.6/src/HSE/Match.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,142 +0,0 @@ -{-# LANGUAGE ViewPatterns, MultiParamTypeClasses, FlexibleInstances #-} - -module HSE.Match( - View(..), Named(..), - (~=), isSym, - App2(App2), PVar_(PVar_), Var_(Var_) - ) where - -import Data.Char -import HSE.Type -import HSE.Util - - -class View a b where - view :: a -> b - - -data App2 = NoApp2 | App2 Exp_ Exp_ Exp_ deriving Show - -instance View Exp_ App2 where - view (fromParen -> InfixApp _ lhs op rhs) = App2 (opExp op) lhs rhs - view (fromParen -> App _ (fromParen -> App _ f x) y) = App2 f x y - view _ = NoApp2 - - -data App1 = NoApp1 | App1 Exp_ Exp_ deriving Show - -instance View Exp_ App1 where - view (fromParen -> App _ f x) = App1 f x - view _ = NoApp1 - -data PVar_ = NoPVar_ | PVar_ String - -instance View Pat_ PVar_ where - view (fromPParen -> PVar _ x) = PVar_ $ fromNamed x - view _ = NoPVar_ - -data Var_ = NoVar_ | Var_ String deriving Eq - -instance View Exp_ Var_ where - view (fromParen -> Var _ (UnQual _ x)) = Var_ $ fromNamed x - view _ = NoVar_ - - -(~=) :: Named a => a -> String -> Bool -(~=) = (==) . fromNamed - - --- | fromNamed will return \"\" when it cannot be represented --- toNamed may crash on \"\" -class Named a where - toNamed :: String -> a - fromNamed :: a -> String - - -isCtor (x:_) = isUpper x || x == ':' -isCtor _ = False - -isSym (x:_) = not $ isAlpha x || x `elem` "_'" -isSym _ = False - - -instance Named (Exp S) where - fromNamed (Var _ x) = fromNamed x - fromNamed (Con _ x) = fromNamed x - fromNamed (List _ []) = "[]" - fromNamed _ = "" - - toNamed "[]" = List an [] - toNamed x | isCtor x = Con an $ toNamed x - | otherwise = Var an $ toNamed x - -instance Named (QName S) where - fromNamed (Special _ Cons{}) = ":" - fromNamed (Special _ UnitCon{}) = "()" - fromNamed (UnQual _ x) = fromNamed x - fromNamed _ = "" - - toNamed ":" = Special an $ Cons an - toNamed x = UnQual an $ toNamed x - -instance Named (Name S) where - fromNamed (Ident _ x) = x - fromNamed (Symbol _ x) = x - - toNamed x | isSym x = Symbol an x - | otherwise = Ident an x - -instance Named (ModuleName S) where - fromNamed (ModuleName _ x) = x - toNamed = ModuleName an - - -instance Named (Pat S) where - fromNamed (PVar _ x) = fromNamed x - fromNamed (PApp _ x []) = fromNamed x - fromNamed (PList _ []) = "[]" - fromNamed _ = "" - - toNamed x | isCtor x = PApp an (toNamed x) [] - | otherwise = PVar an $ toNamed x - - -instance Named (TyVarBind S) where - fromNamed (KindedVar _ x _) = fromNamed x - fromNamed (UnkindedVar _ x) = fromNamed x - toNamed x = UnkindedVar an (toNamed x) - - -instance Named (QOp S) where - fromNamed (QVarOp _ x) = fromNamed x - fromNamed (QConOp _ x) = fromNamed x - toNamed x | isCtor x = QConOp an $ toNamed x - | otherwise = QVarOp an $ toNamed x - -instance Named (Match S) where - fromNamed (Match _ x _ _ _) = fromNamed x - fromNamed (InfixMatch _ _ x _ _ _) = fromNamed x - toNamed = error "No toNamed for Match" - -instance Named (DeclHead S) where - fromNamed (DHead _ x) = fromNamed x - fromNamed (DHInfix _ _ x) = fromNamed x - fromNamed (DHParen _ x) = fromNamed x - fromNamed (DHApp _ x _) = fromNamed x - toNamed = error "No toNamed for DeclHead" - -instance Named (Decl S) where - fromNamed (TypeDecl _ name _) = fromNamed name - fromNamed (DataDecl _ _ _ name _ _) = fromNamed name - fromNamed (GDataDecl _ _ _ name _ _ _) = fromNamed name - fromNamed (TypeFamDecl _ name _ _) = fromNamed name - fromNamed (DataFamDecl _ _ name _) = fromNamed name - fromNamed (ClassDecl _ _ name _ _) = fromNamed name - fromNamed (PatBind _ (PVar _ name) _ _) = fromNamed name - fromNamed (FunBind _ (name:_)) = fromNamed name - fromNamed (ForImp _ _ _ _ name _) = fromNamed name - fromNamed (ForExp _ _ _ name _) = fromNamed name - fromNamed (TypeSig _ (name:_) _) = fromNamed name - fromNamed _ = "" - - toNamed = error "No toNamed for Decl" diff -Nru hlint-2.2.11/src/HSE/Scope.hs hlint-3.1.6/src/HSE/Scope.hs --- hlint-2.2.11/src/HSE/Scope.hs 2020-01-08 09:56:13.000000000 +0000 +++ hlint-3.1.6/src/HSE/Scope.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -{-# LANGUAGE GeneralizedNewtypeDeriving #-} - -module HSE.Scope( - Scope, scopeCreate, scopeImports - ) where - -import Data.Semigroup -import HSE.Type -import HSE.Util -import Data.List -import Data.Maybe -import Prelude - -{- -the hint file can do: - -import Prelude (filter) -import Data.List (filter) -import List (filter) - -then filter on it's own will get expanded to all of them - -import Data.List -import List as Data.List - - -if Data.List.head x ==> x, then that might match List too --} - - --- | Data type representing the modules in scope within a module. --- Created with 'scopeCreate' and queried with 'scopeMatch' and 'scopeMove'. --- Note that the 'mempty' 'Scope' is not equivalent to 'scopeCreate' on an empty module, --- due to the implicit import of 'Prelude'. -newtype Scope = Scope [ImportDecl S] - deriving (Show, Monoid, Semigroup) - --- | Create a 'Scope' value from a module, based on the modules imports. -scopeCreate :: Module SrcSpanInfo -> Scope -scopeCreate xs = Scope $ [prelude | not $ any isPrelude res] ++ res - where - res = [x | x <- moduleImports xs, importPkg x /= Just "hint"] - prelude = ImportDecl an (ModuleName an "Prelude") False False False Nothing Nothing Nothing - isPrelude x = fromModuleName (importModule x) == "Prelude" - - -scopeImports :: Scope -> [ImportDecl S] -scopeImports (Scope x) = x diff -Nru hlint-2.2.11/src/HSE/Type.hs hlint-3.1.6/src/HSE/Type.hs --- hlint-2.2.11/src/HSE/Type.hs 2020-01-08 09:56:13.000000000 +0000 +++ hlint-3.1.6/src/HSE/Type.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,18 +0,0 @@ - -module HSE.Type( - S, - Module_, Decl_, Exp_, Pat_, - module HSE, - module Uniplate - ) where - --- Almost all from the Annotated module, but the fixity resolution from Annotated --- uses the unannotated Assoc enumeration, so export that instead -import Language.Haskell.Exts as HSE hiding (parse, loc, paren) -import Data.Generics.Uniplate.Data as Uniplate - -type S = SrcSpanInfo -type Module_ = Module S -type Decl_ = Decl S -type Exp_ = Exp S -type Pat_ = Pat S diff -Nru hlint-2.2.11/src/HSE/Util.hs hlint-3.1.6/src/HSE/Util.hs --- hlint-2.2.11/src/HSE/Util.hs 2020-02-07 10:36:33.000000000 +0000 +++ hlint-3.1.6/src/HSE/Util.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,420 +0,0 @@ -{-# LANGUAGE FlexibleContexts, TupleSections #-} - -module HSE.Util(module HSE.Util, def) where - -import Control.Monad -import Data.Default -import Data.Tuple.Extra -import Data.List -import Language.Haskell.Exts.Util -import Control.Monad.Trans.State -import qualified Data.Map as Map -import Data.Maybe -import Data.Data hiding (Fixity) -import System.FilePath -import HSE.Type -import Data.Functor -import Prelude - - ---------------------------------------------------------------------- --- ACCESSOR/TESTER - -ellipses :: QName S -ellipses = UnQual an $ Ident an "..." -- Must be an Ident, not a Symbol - -opExp :: QOp S -> Exp_ -opExp (QVarOp s op) = Var s op -opExp (QConOp s op) = Con s op - -expOp :: Exp_ -> Maybe (QOp S) -expOp (Var s op) = Just $ QVarOp s op -expOp (Con s op) = Just $ QConOp s op -expOp _ = Nothing - -moduleDecls :: Module_ -> [Decl_] -moduleDecls (Module _ _ _ _ xs) = xs -moduleDecls _ = [] -- XmlPage/XmlHybrid - -moduleName :: Module_ -> String -moduleName (Module _ Nothing _ _ _) = "Main" -moduleName (Module _ (Just (ModuleHead _ (ModuleName _ x) _ _)) _ _ _) = x -moduleName _ = "" -- XmlPage/XmlHybrid - -moduleImports :: Module_ -> [ImportDecl S] -moduleImports (Module _ _ _ x _) = x -moduleImports _ = [] -- XmlPage/XmlHybrid - -modulePragmas :: Module_ -> [ModulePragma S] -modulePragmas (Module _ _ x _ _) = x -modulePragmas _ = [] -- XmlPage/XmlHybrid - -moduleExtensions :: Module_ -> [Name S] -moduleExtensions x = concat [y | LanguagePragma _ y <- modulePragmas x] - -fromModuleName :: ModuleName S -> String -fromModuleName (ModuleName _ x) = x - -fromChar :: Exp_ -> Maybe Char -fromChar (Lit _ (Char _ x _)) = Just x -fromChar _ = Nothing - -fromPChar :: Pat_ -> Maybe Char -fromPChar (PLit _ _ (Char _ x _)) = Just x -fromPChar _ = Nothing - -fromString :: Exp_ -> Maybe String -fromString (Lit _ (String _ x _)) = Just x -fromString _ = Nothing - -fromPString :: Pat_ -> Maybe String -fromPString (PLit _ _ (String _ x _)) = Just x -fromPString _ = Nothing - -fromParen1 :: Exp_ -> Exp_ -fromParen1 (Paren _ x) = x -fromParen1 x = x - -fromParen :: Exp_ -> Exp_ -fromParen (Paren _ x) = fromParen x -fromParen x = x - -fromPParen :: Pat s -> Pat s -fromPParen (PParen _ x) = fromPParen x -fromPParen x = x - -fromTyParen :: Type s -> Type s -fromTyParen (TyParen _ x) = fromTyParen x -fromTyParen x = x - -fromTyBang :: Type s -> Type s -fromTyBang (TyBang _ _ _ x) = x -fromTyBang x = x - --- is* :: Exp_ -> Bool --- is* :: Decl_ -> Bool -isVar Var{} = True; isVar _ = False -isCon Con{} = True; isCon _ = False -isApp App{} = True; isApp _ = False -isInfixApp InfixApp{} = True; isInfixApp _ = False -isAnyApp x = isApp x || isInfixApp x -isParen Paren{} = True; isParen _ = False -isIf If{} = True; isIf _ = False -isLambda Lambda{} = True; isLambda _ = False -isMDo MDo{} = True; isMDo _ = False -isBoxed Boxed{} = True; isBoxed _ = False -isDerivDecl DerivDecl{} = True; isDerivDecl _ = False -isPBangPat PBangPat{} = True; isPBangPat _ = False -isPFieldPun PFieldPun{} = True; isPFieldPun _ = False -isFieldPun FieldPun{} = True; isFieldPun _ = False -isPWildCard PWildCard{} = True; isPWildCard _ = False -isPFieldWildcard PFieldWildcard{} = True; isPFieldWildcard _ = False -isFieldWildcard FieldWildcard{} = True; isFieldWildcard _ = False -isPViewPat PViewPat{} = True; isPViewPat _ = False -isParComp ParComp{} = True; isParComp _ = False -isTypeApp TypeApp{} = True; isTypeApp _ = False -isPatTypeSig PatTypeSig{} = True; isPatTypeSig _ = False -isQuasiQuote QuasiQuote{} = True; isQuasiQuote _ = False -isTyQuasiQuote TyQuasiQuote{} = True; isTyQuasiQuote _ = False -isSpliceDecl SpliceDecl{} = True; isSpliceDecl _ = False -isNewType NewType{} = True; isNewType _ = False -isRecStmt RecStmt{} = True; isRecStmt _ = False -isClsDefSig ClsDefSig{} = True; isClsDefSig _ = False -isTyBang TyBang{} = True; isTyBang _ = False -isLCase LCase{} = True; isLCase _ = False -isTupleSection TupleSection{} = True; isTupleSection _ = False -isString String{} = True; isString _ = False -isRecUpdate RecUpdate{} = True; isRecUpdate _ = False -isRecConstr RecConstr{} = True; isRecConstr _ = False - -isSection LeftSection{} = True -isSection RightSection{} = True -isSection _ = False - -isPrimLiteral PrimInt{} = True -isPrimLiteral PrimWord{} = True -isPrimLiteral PrimFloat{} = True -isPrimLiteral PrimDouble{} = True -isPrimLiteral PrimChar{} = True -isPrimLiteral PrimString{} = True -isPrimLiteral _ = False - - -allowRightSection x = x `notElem` ["-","#"] -allowLeftSection x = x /= "#" - - -unqual :: QName S -> QName S -unqual (Qual an _ x) = UnQual an x -unqual x = x - -fromQual :: QName a -> Maybe (Name a) -fromQual (Qual _ _ x) = Just x -fromQual (UnQual _ x) = Just x -fromQual _ = Nothing - -isSpecial :: QName S -> Bool -isSpecial Special{} = True; isSpecial _ = False - -isDol :: QOp S -> Bool -isDol (QVarOp _ (UnQual _ (Symbol _ "$"))) = True -isDol _ = False - -isDot :: QOp S -> Bool -isDot (QVarOp _ (UnQual _ (Symbol _ "."))) = True -isDot _ = False - -isDotApp :: Exp_ -> Bool -isDotApp (InfixApp _ _ dot _) | isDot dot = True -isDotApp _ = False - -dotApp :: Exp_ -> Exp_ -> Exp_ -dotApp x = InfixApp an x (QVarOp an $ UnQual an $ Symbol an ".") - -dotApps :: [Exp_] -> Exp_ -dotApps [] = error "HSE.Util.dotApps, does not work on an empty list" -dotApps [x] = x -dotApps (x:xs) = dotApp x (dotApps xs) - -isReturn :: Exp_ -> Bool --- Allow both pure and return, as they have the same semantics -isReturn (Var _ (UnQual _ (Ident _ x))) = x == "return" || x == "pure" -isReturn _ = False - -isLexeme Var{} = True -isLexeme Con{} = True -isLexeme Lit{} = True -isLexeme _ = False - -isAssocLeft AssocLeft{} = True; isAssocLeft _ = False -isAssocNone AssocNone{} = True; isAssocNone _ = False - -isWHNF :: Exp_ -> Bool -isWHNF Con{} = True -isWHNF (Lit _ x) = case x of String{} -> False; Int{} -> False; Frac{} -> False; _ -> True -isWHNF Lambda{} = True -isWHNF Tuple{} = True -isWHNF List{} = True -isWHNF (Paren _ x) = isWHNF x -isWHNF (ExpTypeSig _ x _) = isWHNF x --- other (unknown) constructors may have bang patterns in them, so approximate -isWHNF (App _ c@Con{} _) | prettyPrint c `elem` ["Just","Left","Right"] = True -isWHNF _ = False - - --- | Like needBracket, but with a special case for a . b . b, which --- was removed from haskell-src-exts-util-0.2.2 -needBracketOld :: Int -> Exp_ -> Exp_ -> Bool -needBracketOld i parent child - | isDotApp parent, isDotApp child, i == 1 = False - | otherwise = needBracket i parent child - -transformBracketOld :: (Exp_ -> Maybe Exp_) -> Exp_ -> Exp_ -transformBracketOld op = snd . g - where - g = f . descendBracketOld g - f x = maybe (False,x) (True,) (op x) - --- | Descend, and if something changes then add/remove brackets appropriately -descendBracketOld :: (Exp_ -> (Bool, Exp_)) -> Exp_ -> Exp_ -descendBracketOld op x = descendIndex g x - where - g i y = if a then f i b else b - where (a,b) = op y - - f i (Paren _ y) | not $ needBracketOld i x y = y - f i y | needBracketOld i x y = addParen y - f _ y = y - -descendIndex :: Data a => (Int -> a -> a) -> a -> a -descendIndex f x = flip evalState 0 $ flip descendM x $ \y -> do - i <- get - modify (+1) - return $ f i y - - ---------------------------------------------------------------------- --- HSE FUNCTIONS - -getEquations :: Decl s -> [Decl s] -getEquations (FunBind s xs) = map (FunBind s . (:[])) xs -getEquations x@PatBind{} = [toFunBind x] -getEquations x = [x] - - -toFunBind :: Decl s -> Decl s -toFunBind (PatBind s (PVar _ name) bod bind) = FunBind s [Match s name [] bod bind] -toFunBind x = x - - --- case and if both have branches, nothing else does -replaceBranches :: Exp s -> ([Exp s], [Exp s] -> Exp s) -replaceBranches (If s a b c) = ([b,c], \[b,c] -> If s a b c) -replaceBranches (Case s a bs) = (concatMap f bs, Case s a . g bs) - where - f (Alt _ _ (UnGuardedRhs _ x) _) = [x] - f (Alt _ _ (GuardedRhss _ xs) _) = [x | GuardedRhs _ _ x <- xs] - g (Alt s1 a (UnGuardedRhs s2 _) b:rest) (x:xs) = Alt s1 a (UnGuardedRhs s2 x) b : g rest xs - g (Alt s1 a (GuardedRhss s2 ns) b:rest) xs = - Alt s1 a (GuardedRhss s2 [GuardedRhs a b x | (GuardedRhs a b _,x) <- zip ns as]) b : g rest bs - where (as,bs) = splitAt (length ns) xs - g [] [] = [] - g _ _ = error "HSE.Util.replaceBranches: internal invariant failed, lists are of differing lengths" -replaceBranches x = ([], \[] -> x) - - ---------------------------------------------------------------------- --- VECTOR APPLICATION - - -apps :: [Exp_] -> Exp_ -apps = foldl1 (App an) - -fromApps :: Exp_ -> [Exp_] -fromApps = map fst . fromAppsWithLoc - -fromAppsWithLoc :: Exp_ -> [(Exp_, S)] -fromAppsWithLoc (App l x y) = fromAppsWithLoc x ++ [(y, l)] -fromAppsWithLoc x = [(x, ann x)] - - --- Rule for the Uniplate Apps functions --- Given (f a) b, consider the children to be: children f ++ [a,b] - -childrenApps :: Exp_ -> [Exp_] -childrenApps (App s x y) = childrenApps x ++ [y] -childrenApps x = children x - - -descendApps :: (Exp_ -> Exp_) -> Exp_ -> Exp_ -descendApps f (App s x y) = App s (descendApps f x) (f y) -descendApps f x = descend f x - - -descendAppsM :: Monad m => (Exp_ -> m Exp_) -> Exp_ -> m Exp_ -descendAppsM f (App s x y) = liftM2 (App s) (descendAppsM f x) (f y) -descendAppsM f x = descendM f x - - -universeApps :: Exp_ -> [Exp_] -universeApps x = x : concatMap universeApps (childrenApps x) - -transformApps :: (Exp_ -> Exp_) -> Exp_ -> Exp_ -transformApps f = f . descendApps (transformApps f) - -transformAppsM :: Monad m => (Exp_ -> m Exp_) -> Exp_ -> m Exp_ -transformAppsM f x = f =<< descendAppsM (transformAppsM f) x - - ---------------------------------------------------------------------- --- UNIPLATE FUNCTIONS - -universeS :: (Data x, Data (f S)) => x -> [f S] -universeS = universeBi - -childrenS :: (Data x, Data (f S)) => x -> [f S] -childrenS = childrenBi - - --- return the parent along with the child -universeParentExp :: Data a => a -> [(Maybe (Int, Exp_), Exp_)] -universeParentExp xs = concat [(Nothing, x) : f x | x <- childrenBi xs] - where f p = concat [(Just (i,p), c) : f c | (i,c) <- zip [0..] $ children p] - - ---------------------------------------------------------------------- --- SRCLOC FUNCTIONS - -showSrcLoc :: SrcLoc -> String -showSrcLoc (SrcLoc file line col) = take 1 file ++ f (drop 1 file) ++ ":" ++ show line ++ ":" ++ show col - where f (x:y:zs) | isPathSeparator x && isPathSeparator y = f $ x:zs - f (x:xs) = x : f xs - f [] = [] - -an :: SrcSpanInfo -an = def - -dropAnn :: Functor f => f SrcSpanInfo -> f () -dropAnn = void - ---------------------------------------------------------------------- --- SRCLOC EQUALITY - --- enforce all being on S, as otherwise easy to =~= on a Just, and get the wrong functor - -x /=~= y = not $ x =~= y - -elem_, notElem_ :: (Annotated f, Eq (f ())) => f S -> [f S] -> Bool -elem_ x = any (x =~=) -notElem_ x = not . elem_ x - -nub_ :: (Annotated f, Eq (f ())) => [f S] -> [f S] -nub_ = nubBy (=~=) - -delete_ :: (Annotated f, Eq (f ())) => f S -> [f S] -> [f S] -delete_ = deleteBy (=~=) - -intersect_ :: (Annotated f, Eq (f ())) => [f S] -> [f S] -> [f S] -intersect_ = intersectBy (=~=) - -eqList, neqList :: (Annotated f, Eq (f ())) => [f S] -> [f S] -> Bool -neqList x y = not $ eqList x y -eqList (x:xs) (y:ys) = x =~= y && eqList xs ys -eqList [] [] = True -eqList _ _ = False - -eqMaybe:: (Annotated f, Eq (f ())) => Maybe (f S) -> Maybe (f S) -> Bool -eqMaybe (Just x) (Just y) = x =~= y -eqMaybe Nothing Nothing = True -eqMaybe _ _ = False - - ---------------------------------------------------------------------- --- FIXITIES - -getFixity :: Decl a -> [Fixity] -getFixity (InfixDecl sl a mp ops) = [Fixity (void a) (fromMaybe 9 mp) (UnQual () $ void $ f op) | op <- ops] - where f (VarOp _ x) = x - f (ConOp _ x) = x -getFixity _ = [] - -toInfixDecl :: Fixity -> Decl () -toInfixDecl (Fixity a b c) = InfixDecl () a (Just b) $ maybeToList $ VarOp () <$> fromQual c - - - --- | This extension implies the following extensions -extensionImplies :: Extension -> [Extension] -extensionImplies = \x -> Map.findWithDefault [] x mp - where mp = Map.fromList extensionImplications - --- | This extension is implied by the following extensions -extensionImpliedBy :: Extension -> [Extension] -extensionImpliedBy = \x -> Map.findWithDefault [] x mp - where mp = Map.fromListWith (++) [(b, [a]) | (a,bs) <- extensionImplications, b <- bs] - --- | (a, bs) means extension a implies all of bs. --- Taken from https://downloads.haskell.org/~ghc/master/users-guide/glasgow_exts.html#language-options --- In the GHC source at DynFlags.impliedXFlags -extensionImplications :: [(Extension, [Extension])] -extensionImplications = map (first EnableExtension) $ - (RebindableSyntax, [DisableExtension ImplicitPrelude]) : - map (second (map EnableExtension)) - [ (DerivingVia , [DerivingStrategies]) - , (RecordWildCards , [DisambiguateRecordFields]) - , (ExistentialQuantification, [ExplicitForAll]) - , (FlexibleInstances , [TypeSynonymInstances]) - , (FunctionalDependencies , [MultiParamTypeClasses]) - , (GADTs , [MonoLocalBinds]) - , (IncoherentInstances , [OverlappingInstances]) --- Incorrect, see https://github.com/ndmitchell/hlint/issues/587 --- , (ImplicitParams , [FlexibleContexts, FlexibleInstances]) - , (ImpredicativeTypes , [ExplicitForAll, RankNTypes]) - , (LiberalTypeSynonyms , [ExplicitForAll]) - , (PolyKinds , [KindSignatures]) - , (RankNTypes , [ExplicitForAll]) - , (ScopedTypeVariables , [ExplicitForAll]) - , (TypeOperators , [ExplicitNamespaces]) - , (TypeFamilies , [ExplicitNamespaces, KindSignatures, MonoLocalBinds]) - , (TypeFamilyDependencies , [ExplicitNamespaces, KindSignatures, MonoLocalBinds, TypeFamilies]) - ] diff -Nru hlint-2.2.11/src/Idea.hs hlint-3.1.6/src/Idea.hs --- hlint-2.2.11/src/Idea.hs 2019-09-24 17:55:46.000000000 +0000 +++ hlint-3.1.6/src/Idea.hs 2020-05-21 15:55:34.000000000 +0000 @@ -2,8 +2,8 @@ module Idea( Idea(..), - rawIdea, rawIdea', idea, idea', suggest, suggest', warn, warn',ignore, ignore', - rawIdeaN, rawIdeaN', suggestN, suggestN', ignoreN, ignoreN', ignoreNoSuggestion', + rawIdea, idea, suggest, suggestRemove, ideaRemove, warn, ignore, + rawIdeaN, suggestN, ignoreNoSuggestion, showIdeasJson, showANSI, Note(..), showNotes, Severity(..), @@ -11,15 +11,16 @@ import Data.Functor import Data.List.Extra -import HSE.All import Config.Type import HsColour import Refact.Types hiding (SrcSpan) import qualified Refact.Types as R import Prelude -import qualified SrcLoc as GHC -import qualified Outputable -import qualified GHC.Util as GHC +import SrcLoc +import Outputable +import GHC.Util + +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable -- | An idea suggest by a 'Hint'. data Idea = Idea @@ -45,9 +46,9 @@ ,("severity", str $ show ideaSeverity) ,("hint", str ideaHint) ,("file", str srcSpanFilename) - ,("startLine", show srcSpanStartLine) + ,("startLine", show srcSpanStartLine') ,("startColumn", show srcSpanStartColumn) - ,("endLine", show srcSpanEndLine) + ,("endLine", show srcSpanEndLine') ,("endColumn", show srcSpanEndColumn) ,("from", str ideaFrom) ,("to", maybe "null" str ideaTo) @@ -71,7 +72,7 @@ showEx :: (String -> String) -> Idea -> String showEx tt Idea{..} = unlines $ - [showSrcLoc (getPointLoc ideaSpan) ++ ": " ++ (if ideaHint == "" then "" else show ideaSeverity ++ ": " ++ ideaHint)] ++ + [showSrcSpan ideaSpan ++ ": " ++ (if ideaHint == "" then "" else show ideaSeverity ++ ": " ++ ideaHint)] ++ f "Found" (Just ideaFrom) ++ f "Perhaps" ideaTo ++ ["Note: " ++ n | let n = showNotes ideaNote, n /= ""] where @@ -84,73 +85,41 @@ rawIdea :: Severity -> String -> SrcSpan -> String -> Maybe String -> [Note]-> [Refactoring R.SrcSpan] -> Idea rawIdea = Idea [] [] -rawIdea' :: Severity -> String -> GHC.SrcSpan -> String -> Maybe String -> [Note]-> [Refactoring R.SrcSpan] -> Idea -rawIdea' a b c = Idea [] [] a b (ghcSpanToHSE c) - rawIdeaN :: Severity -> String -> SrcSpan -> String -> Maybe String -> [Note] -> Idea rawIdeaN a b c d e f = Idea [] [] a b c d e f [] -rawIdeaN' :: Severity -> String -> GHC.SrcSpan -> String -> Maybe String -> [Note] -> Idea -rawIdeaN' a b c d e f = Idea [] [] a b (ghcSpanToHSE c) d e f [] - -idea :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) => - Severity -> String -> ast SrcSpanInfo -> a -> [Refactoring R.SrcSpan] -> Idea -idea severity hint from to = rawIdea severity hint (srcInfoSpan $ ann from) (f from) (Just $ f to) [] - where f = trimStart . prettyPrint - -idea' :: (GHC.HasSrcSpan a, Outputable.Outputable a, GHC.HasSrcSpan b, Outputable.Outputable b) => +idea :: (HasSrcSpan a, Outputable.Outputable a, HasSrcSpan b, Outputable.Outputable b) => Severity -> String -> a -> b -> [Refactoring R.SrcSpan] -> Idea -idea' severity hint from to = - rawIdea severity hint (ghcSpanToHSE (GHC.getLoc from)) (GHC.unsafePrettyPrint from) (Just $ GHC.unsafePrettyPrint to) [] +idea severity hint from to = + rawIdea severity hint (getLoc from) (unsafePrettyPrint from) (Just $ unsafePrettyPrint to) [] -suggest :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) => - String -> ast SrcSpanInfo -> a -> [Refactoring R.SrcSpan] -> Idea -suggest = idea Suggestion +-- Construct an Idea that suggests "Perhaps you should remove it." +ideaRemove :: Severity -> String -> SrcSpan -> String -> [Refactoring R.SrcSpan] -> Idea +ideaRemove severity hint span from = rawIdea severity hint span from (Just "") [] -suggest' :: (GHC.HasSrcSpan a, Outputable.Outputable a, GHC.HasSrcSpan b, Outputable.Outputable b) => +suggest :: (HasSrcSpan a, Outputable.Outputable a, HasSrcSpan b, Outputable.Outputable b) => String -> a -> b -> [Refactoring R.SrcSpan] -> Idea -suggest' = idea' Suggestion +suggest = idea Suggestion -warn :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) => - String -> ast SrcSpanInfo -> a -> [Refactoring R.SrcSpan] -> Idea -warn = idea Warning +suggestRemove :: String -> SrcSpan -> String -> [Refactoring R.SrcSpan] -> Idea +suggestRemove = ideaRemove Suggestion -warn' :: (GHC.HasSrcSpan a, Outputable.Outputable a, GHC.HasSrcSpan b, Outputable.Outputable b) => +warn :: (HasSrcSpan a, Outputable.Outputable a, HasSrcSpan b, Outputable.Outputable b) => String -> a -> b -> [Refactoring R.SrcSpan] -> Idea -warn' = idea' Warning +warn = idea Warning -ignoreNoSuggestion' :: (GHC.HasSrcSpan a, Outputable.Outputable a) +ignoreNoSuggestion :: (HasSrcSpan a, Outputable.Outputable a) => String -> a -> Idea -ignoreNoSuggestion' hint x = rawIdeaN Ignore hint (ghcSpanToHSE (GHC.getLoc x)) (GHC.unsafePrettyPrint x) Nothing [] - -ignore :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) => - String -> ast SrcSpanInfo -> a -> [Refactoring R.SrcSpan] -> Idea -ignore = idea Ignore +ignoreNoSuggestion hint x = rawIdeaN Ignore hint (getLoc x) (unsafePrettyPrint x) Nothing [] -ignore' :: (GHC.HasSrcSpan a, Outputable.Outputable a) => +ignore :: (HasSrcSpan a, Outputable.Outputable a) => String -> a -> a -> [Refactoring R.SrcSpan] -> Idea -ignore' = idea' Ignore - -ideaN :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) => - Severity -> String -> ast SrcSpanInfo -> a -> Idea -ideaN severity hint from to = idea severity hint from to [] +ignore = idea Ignore -ideaN' :: (GHC.HasSrcSpan a, Outputable.Outputable a) => +ideaN :: (HasSrcSpan a, Outputable.Outputable a) => Severity -> String -> a -> a -> Idea -ideaN' severity hint from to = idea' severity hint from to [] - -suggestN :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) => - String -> ast SrcSpanInfo -> a -> Idea -suggestN = ideaN Suggestion +ideaN severity hint from to = idea severity hint from to [] -suggestN' :: (GHC.HasSrcSpan a, Outputable.Outputable a) => +suggestN :: (HasSrcSpan a, Outputable.Outputable a) => String -> a -> a -> Idea -suggestN' = ideaN' Suggestion - -ignoreN :: (Annotated ast, Pretty a, Pretty (ast SrcSpanInfo)) => - String -> ast SrcSpanInfo -> a -> Idea -ignoreN = ideaN Ignore - -ignoreN' :: (GHC.HasSrcSpan a, Outputable.Outputable a) => - String -> a -> a -> Idea -ignoreN' = ideaN' Ignore +suggestN = ideaN Suggestion diff -Nru hlint-2.2.11/src/Language/Haskell/HLint3.hs hlint-3.1.6/src/Language/Haskell/HLint3.hs --- hlint-2.2.11/src/Language/Haskell/HLint3.hs 2019-06-26 11:42:40.000000000 +0000 +++ hlint-3.1.6/src/Language/Haskell/HLint3.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ - --- | A reexport of "Language.Haskell.HLint4" for compatibility purposes. -module Language.Haskell.HLint3( - module Language.Haskell.HLint4 - ) where - -import Language.Haskell.HLint4 diff -Nru hlint-2.2.11/src/Language/Haskell/HLint4.hs hlint-3.1.6/src/Language/Haskell/HLint4.hs --- hlint-2.2.11/src/Language/Haskell/HLint4.hs 2020-01-21 14:43:12.000000000 +0000 +++ hlint-3.1.6/src/Language/Haskell/HLint4.hs 1970-01-01 00:00:00.000000000 +0000 @@ -1,152 +0,0 @@ -{-# LANGUAGE PatternGuards, RecordWildCards #-} - --- | /WARNING: This module represents the evolving version of the HLint API./ --- /It will be renamed to drop the "4" in the next major version./ --- --- This module provides a way to apply HLint hints. If you want to just run @hlint@ in-process --- and collect the results see 'hlint'. If you want to approximate the @hlint@ experience with --- a more structured API try: --- --- @ --- (flags, classify, hint) <- 'autoSettings' --- Right m <- 'parseModuleEx' flags \"MyFile.hs\" Nothing --- print $ 'applyHints' classify hint [m] --- @ -module Language.Haskell.HLint4( - hlint, applyHints, - -- * Idea data type - Idea(..), Severity(..), Note(..), - -- * Settings - Classify(..), - getHLintDataDir, autoSettings, argsSettings, - findSettings, readSettingsFile, - -- * Hints - Hint, resolveHints, - -- * Parse files - ModuleEx, parseModuleEx, createModuleEx, defaultParseFlags, parseFlagsAddFixities, ParseError(..), ParseFlags(..), CppFlags(..) - ) where - -import Config.Type -import Config.Read -import Idea -import qualified Apply as H -import HLint -import HSE.All -import Hint.All hiding (resolveHints) -import qualified Hint.All as H -import qualified ApiAnnotation as GHC -import qualified HsSyn as GHC -import SrcLoc -import CmdLine -import Paths_hlint -import qualified Language.Haskell.GhclibParserEx.Fixity as GhclibParserEx - -import Data.List.Extra -import Data.Maybe -import System.FilePath -import Data.Functor -import Prelude - - --- | Get the Cabal configured data directory of HLint. -getHLintDataDir :: IO FilePath -getHLintDataDir = getDataDir - - --- | The function produces a tuple containg 'ParseFlags' (for 'parseModuleEx'), --- and 'Classify' and 'Hint' for 'applyHints'. --- It approximates the normal HLint configuration steps, roughly: --- --- 1. Use 'findSettings' with 'readSettingsFile' to find and load the HLint settings files. --- --- 1. Use 'parseFlagsAddFixities' and 'resolveHints' to transform the outputs of 'findSettings'. --- --- If you want to do anything custom (e.g. using a different data directory, storing intermediate outputs, --- loading hints from a database) you are expected to copy and paste this function, then change it to your needs. -autoSettings :: IO (ParseFlags, [Classify], Hint) -autoSettings = do - (fixities, classify, hints) <- findSettings (readSettingsFile Nothing) Nothing - return (parseFlagsAddFixities fixities defaultParseFlags, classify, hints) - - --- | The identity function. In previous versions of HLint this function was useful. Now, it isn't. -resolveHints :: Hint -> Hint -resolveHints = id - --- | A version of 'autoSettings' which respects some of the arguments supported by HLint. --- If arguments unrecognised by HLint are used it will result in an error. --- Arguments which have no representation in the return type are silently ignored. -argsSettings :: [String] -> IO (ParseFlags, [Classify], Hint) -argsSettings args = do - cmd <- getCmd args - case cmd of - CmdMain{..} -> do - -- FIXME: Two things that could be supported (but aren't) are 'cmdGivenHints' and 'cmdWithHints'. - (_,settings) <- readAllSettings args cmd - let (fixities, classify, hints) = splitSettings settings - let flags = parseFlagsSetLanguage (cmdExtensions cmd) $ parseFlagsAddFixities fixities $ - defaultParseFlags{cppFlags = cmdCpp cmd} - let ignore = [Classify Ignore x "" "" | x <- cmdIgnore] - return (flags, classify ++ ignore, hints) - _ -> error "Can only invoke autoSettingsArgs with the root process" - - --- | Given a directory (or 'Nothing' to imply 'getHLintDataDir'), and a module name --- (e.g. @HLint.Default@), find the settings file associated with it, returning the --- name of the file, and (optionally) the contents. --- --- This function looks for all settings files starting with @HLint.@ in the directory --- argument, and all other files relative to the current directory. -readSettingsFile :: Maybe FilePath -> String -> IO (FilePath, Maybe String) -readSettingsFile dir x - | takeExtension x `elem` [".yml",".yaml"] = do - dir <- maybe getHLintDataDir return dir - return (dir x, Nothing) - | Just x <- "HLint." `stripPrefix` x = do - dir <- maybe getHLintDataDir return dir - return (dir x <.> "hs", Nothing) - | otherwise = return (x <.> "hs", Nothing) - - --- | Given a function to load a module (typically 'readSettingsFile'), and a module to start from --- (defaults to @hlint.yaml@) find the information from all settings files. -findSettings :: (String -> IO (FilePath, Maybe String)) -> Maybe String -> IO ([Fixity], [Classify], Hint) -findSettings load start = do - (file,contents) <- load $ fromMaybe "hlint.yaml" start - splitSettings <$> readFilesConfig [(file,contents)] - --- | Split a list of 'Setting' for separate use in parsing and hint resolution -splitSettings :: [Setting] -> ([Fixity], [Classify], Hint) -splitSettings xs = - ([x | Infix x <- xs] - ,[x | SettingClassify x <- xs] - ,H.resolveHints $ [Right x | SettingMatchExp x <- xs] ++ map Left [minBound..maxBound]) - - --- | Given a way of classifying results, and a 'Hint', apply to a set of modules generating a list of 'Idea's. --- The 'Idea' values will be ordered within a file. --- --- Given a set of modules, it may be faster to pass each to 'applyHints' in a singleton list. --- When given multiple modules at once this function attempts to find hints between modules, --- which is slower and often pointless (by default HLint passes modules singularly, using --- @--cross@ to pass all modules together). -applyHints :: [Classify] -> Hint -> [ModuleEx] -> [Idea] -applyHints = H.applyHints - --- | Snippet from the documentation, if this changes, update the documentation -_docs :: IO () -_docs = do - (flags, classify, hint) <- autoSettings - Right m <- parseModuleEx flags "MyFile.hs" Nothing - print $ applyHints classify hint [m] - - --- | Create a 'ModuleEx' from GHC annotations and module tree. Note --- that any hints that work on the @haskell-src-exts@ won't work. It --- is assumed the incoming parse module has not been adjusted to --- account for operator fixities. -createModuleEx:: GHC.ApiAnns -> Located (GHC.HsModule GHC.GhcPs) -> ModuleEx -createModuleEx anns ast = - -- Use builtin fixities. - ModuleEx empty [] (GhclibParserEx.applyFixities [] ast) anns - where empty = Module an Nothing [] [] [] diff -Nru hlint-2.2.11/src/Language/Haskell/HLint.hs hlint-3.1.6/src/Language/Haskell/HLint.hs --- hlint-2.2.11/src/Language/Haskell/HLint.hs 2017-02-09 21:57:20.000000000 +0000 +++ hlint-3.1.6/src/Language/Haskell/HLint.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,43 +1,155 @@ -{-| -/WARNING: This module represents the old version of the HLint API./ -/It will be deleted in favour of "Language.Haskell.HLint3" in the next major version./ +{-# LANGUAGE PatternGuards, RecordWildCards #-} -This module provides a library interface to HLint, strongly modelled on the command line interface. --} - -module Language.Haskell.HLint(hlint, Suggestion, suggestionLocation, suggestionSeverity, Severity(..)) where +-- | This module provides a way to apply HLint hints. If you want to just run @hlint@ in-process +-- and collect the results see 'hlint'. +-- +-- If you want to approximate the @hlint@ experience with +-- a more structured API try: +-- +-- @ +-- (flags, classify, hint) <- 'autoSettings' +-- Right m <- 'parseModuleEx' flags \"MyFile.hs\" Nothing +-- print $ 'applyHints' classify hint [m] +-- @ +module Language.Haskell.HLint( + -- * Generate hints + hlint, applyHints, + -- * Idea data type + Idea(..), Severity(..), Note(..), unpackSrcSpan, + -- * Settings + Classify(..), + getHLintDataDir, autoSettings, argsSettings, + findSettings, readSettingsFile, + -- * Hints + Hint, + -- * Modules + ModuleEx, parseModuleEx, createModuleEx, ParseError(..), + -- * Parse flags + defaultParseFlags, + ParseFlags(..), CppFlags(..), FixityInfo, + parseFlagsAddFixities, + ) where -import qualified HLint import Config.Type +import Config.Read +import Control.Exception.Extra import Idea -import HSE.All - - --- | This function takes a list of command line arguments, and returns the given suggestions. --- To see a list of arguments type @hlint --help@ at the console. --- This function writes to the stdout/stderr streams, unless @--quiet@ is specified. +import qualified Apply as H +import HLint +import Fixity +import FastString +import GHC.All +import Hint.All hiding (resolveHints) +import qualified Hint.All as H +import SrcLoc +import CmdLine +import Paths_hlint + +import Data.List.Extra +import Data.Maybe +import System.FilePath +import Data.Functor +import Prelude + + +-- | Get the Cabal configured data directory of HLint. +getHLintDataDir :: IO FilePath +getHLintDataDir = getDataDir + + +-- | The function produces a tuple containg 'ParseFlags' (for 'parseModuleEx'), +-- and 'Classify' and 'Hint' for 'applyHints'. +-- It approximates the normal HLint configuration steps, roughly: -- --- As an example: +-- 1. Use 'findSettings' with 'readSettingsFile' to find and load the HLint settings files. -- --- > do hints <- hlint ["src", "--ignore=Use map","--quiet"] --- > when (length hints > 3) $ error "Too many hints!" -hlint :: [String] -> IO [Suggestion] -hlint = fmap (map Suggestion_) . HLint.hlint - - - --- | A suggestion - the @Show@ instance is of particular use. -newtype Suggestion = Suggestion_ {fromSuggestion :: Idea} - deriving (Eq,Ord) - -instance Show Suggestion where - show = show . fromSuggestion +-- 1. Use 'parseFlagsAddFixities' and 'resolveHints' to transform the outputs of 'findSettings'. +-- +-- If you want to do anything custom (e.g. using a different data directory, storing intermediate outputs, +-- loading hints from a database) you are expected to copy and paste this function, then change it to your needs. +autoSettings :: IO (ParseFlags, [Classify], Hint) +autoSettings = do + (fixities, classify, hints) <- findSettings (readSettingsFile Nothing) Nothing + pure (parseFlagsAddFixities fixities defaultParseFlags, classify, hints) + + +-- | A version of 'autoSettings' which respects some of the arguments supported by HLint. +-- If arguments unrecognised by HLint are used it will result in an error. +-- Arguments which have no representation in the return type are silently ignored. +argsSettings :: [String] -> IO (ParseFlags, [Classify], Hint) +argsSettings args = do + cmd <- getCmd args + case cmd of + CmdMain{..} -> do + -- FIXME: One thing that could be supported (but isn't) is 'cmdGivenHints' + (_,settings) <- readAllSettings args cmd + let (fixities, classify, hints) = splitSettings settings + let flags = parseFlagsSetLanguage (cmdExtensions cmd) $ parseFlagsAddFixities fixities $ + defaultParseFlags{cppFlags = cmdCpp cmd} + let ignore = [Classify Ignore x "" "" | x <- cmdIgnore] + pure (flags, classify ++ ignore, hints) + _ -> errorIO "Can only invoke autoSettingsArgs with the root process" + + +-- | Given a directory (or 'Nothing' to imply 'getHLintDataDir'), and a module name +-- (e.g. @HLint.Default@), find the settings file associated with it, returning the +-- name of the file, and (optionally) the contents. +-- +-- This function looks for all settings files starting with @HLint.@ in the directory +-- argument, and all other files relative to the current directory. +readSettingsFile :: Maybe FilePath -> String -> IO (FilePath, Maybe String) +readSettingsFile dir x + | takeExtension x `elem` [".yml",".yaml"] = do + dir <- maybe getHLintDataDir pure dir + pure (dir x, Nothing) + | Just x <- "HLint." `stripPrefix` x = do + dir <- maybe getHLintDataDir pure dir + pure (dir x <.> "hs", Nothing) + | otherwise = pure (x <.> "hs", Nothing) + + +-- | Given a function to load a module (typically 'readSettingsFile'), and a module to start from +-- (defaults to @hlint.yaml@) find the information from all settings files. +findSettings :: (String -> IO (FilePath, Maybe String)) -> Maybe String -> IO ([FixityInfo], [Classify], Hint) +findSettings load start = do + (file,contents) <- load $ fromMaybe "hlint.yaml" start + splitSettings <$> readFilesConfig [(file,contents)] + +-- | Split a list of 'Setting' for separate use in parsing and hint resolution +splitSettings :: [Setting] -> ([FixityInfo], [Classify], Hint) +splitSettings xs = + ([x | Infix x <- xs] + ,[x | SettingClassify x <- xs] + ,H.resolveHints $ [Right x | SettingMatchExp x <- xs] ++ map Left enumerate) --- | From a suggestion, extract the file location it refers to. -suggestionLocation :: Suggestion -> SrcLoc -suggestionLocation = getPointLoc . ideaSpan . fromSuggestion +-- | Given a way of classifying results, and a 'Hint', apply to a set of modules generating a list of 'Idea's. +-- The 'Idea' values will be ordered within a file. +-- +-- Given a set of modules, it may be faster to pass each to 'applyHints' in a singleton list. +-- When given multiple modules at once this function attempts to find hints between modules, +-- which is slower and often pointless (by default HLint passes modules singularly, using +-- @--cross@ to pass all modules together). +applyHints :: [Classify] -> Hint -> [ModuleEx] -> [Idea] +applyHints = H.applyHints + +-- | Snippet from the documentation, if this changes, update the documentation +_docs :: IO () +_docs = do + (flags, classify, hint) <- autoSettings + Right m <- parseModuleEx flags "MyFile.hs" Nothing + print $ applyHints classify hint [m] --- | From a suggestion, determine how severe it is. -suggestionSeverity :: Suggestion -> Severity -suggestionSeverity = ideaSeverity . fromSuggestion +-- | Unpack a 'SrcSpan' value. Useful to allow using the 'Idea' information without +-- adding a dependency on @ghc@ or @ghc-lib-parser@. Unpacking gives: +-- +-- > (filename, (startLine, startCol), (endLine, endCol)) +-- +-- Following the GHC API, he end column is the column /after/ the end of the error. +-- Lines and columns are 1-based. Returns 'Nothing' if there is no helpful location information. +unpackSrcSpan :: SrcSpan -> Maybe (FilePath, (Int, Int), (Int, Int)) +unpackSrcSpan (RealSrcSpan x) = Just + (unpackFS $ srcSpanFile x + ,(srcSpanStartLine x, srcSpanStartCol x) + ,(srcSpanEndLine x, srcSpanEndCol x)) +unpackSrcSpan _ = Nothing diff -Nru hlint-2.2.11/src/Main.hs hlint-3.1.6/src/Main.hs --- hlint-2.2.11/src/Main.hs 2019-05-26 08:57:42.000000000 +0000 +++ hlint-3.1.6/src/Main.hs 2020-03-28 12:44:17.000000000 +0000 @@ -1,7 +1,7 @@ module Main(main) where -import Language.Haskell.HLint3 +import Language.Haskell.HLint import Control.Monad import System.Environment import System.Exit diff -Nru hlint-2.2.11/src/Parallel.hs hlint-3.1.6/src/Parallel.hs --- hlint-2.2.11/src/Parallel.hs 2017-02-02 22:42:08.000000000 +0000 +++ hlint-3.1.6/src/Parallel.hs 2020-03-05 11:25:03.000000000 +0000 @@ -3,7 +3,7 @@ import Control.Parallel.Strategies parallel :: [IO [a]] -> IO [[a]] -parallel = return . withStrategy (parList $ seqList r0) . map unsafePerformIO +parallel = pure . withStrategy (parList $ seqList r0) . map unsafePerformIO However, this version performs about 10% slower with 2 processors in GHC 6.12.1 -} @@ -21,11 +21,11 @@ parallel1 :: [IO a] -> IO [a] -parallel1 [] = return [] +parallel1 [] = pure [] parallel1 (x:xs) = do x2 <- x xs2 <- unsafeInterleaveIO $ parallel1 xs - return $ x2:xs2 + pure $ x2:xs2 parallelN :: Int -> [IO a] -> IO [a] @@ -40,7 +40,7 @@ f chan = do v <- readChan chan case v of - Nothing -> return () + Nothing -> pure () Just (m,x) -> do putMVar m =<< try x f chan diff -Nru hlint-2.2.11/src/Refact.hs hlint-3.1.6/src/Refact.hs --- hlint-2.2.11/src/Refact.hs 2019-09-24 17:55:46.000000000 +0000 +++ hlint-3.1.6/src/Refact.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,34 +1,66 @@ +{-# LANGUAGE LambdaCase #-} + module Refact ( toRefactSrcSpan - , toSS, toSS' - , toSrcSpan' + , toSS + , checkRefactor, refactorPath, runRefactoring ) where +import Control.Exception.Extra +import Control.Monad +import Data.Maybe +import Data.Version.Extra +import GHC.LanguageExtensions.Type +import System.Directory.Extra +import System.Exit +import System.IO.Extra +import System.Process.Extra import qualified Refact.Types as R -import HSE.All import qualified SrcLoc as GHC -toRefactSrcSpan :: SrcSpan -> R.SrcSpan -toRefactSrcSpan ss = R.SrcSpan (srcSpanStartLine ss) - (srcSpanStartColumn ss) - (srcSpanEndLine ss) - (srcSpanEndColumn ss) - -toSS :: Annotated a => a S -> R.SrcSpan -toSS = toRefactSrcSpan . srcInfoSpan . ann - --- | Don't crash in case ghc gives us a \"fake\" span, --- opting instead to show @0 0 0 0@ coordinates. -toSrcSpan' :: GHC.HasSrcSpan a => a -> R.SrcSpan -toSrcSpan' x = case GHC.getLoc x of +toRefactSrcSpan :: GHC.SrcSpan -> R.SrcSpan +toRefactSrcSpan = \case GHC.RealSrcSpan span -> R.SrcSpan (GHC.srcSpanStartLine span) (GHC.srcSpanStartCol span) (GHC.srcSpanEndLine span) (GHC.srcSpanEndCol span) GHC.UnhelpfulSpan _ -> - R.SrcSpan 0 0 0 0 + R.SrcSpan (-1) (-1) (-1) (-1) -toSS' :: GHC.HasSrcSpan e => e -> R.SrcSpan -toSS' = toRefactSrcSpan . ghcSpanToHSE . GHC.getLoc +-- | Don't crash in case ghc gives us a \"fake\" span, +-- opting instead to show @-1 -1 -1 -1@ coordinates. +toSS :: GHC.HasSrcSpan a => a -> R.SrcSpan +toSS = toRefactSrcSpan . GHC.getLoc + +checkRefactor :: Maybe FilePath -> IO FilePath +checkRefactor = refactorPath >=> either errorIO pure + +refactorPath :: Maybe FilePath -> IO (Either String FilePath) +refactorPath rpath = do + let excPath = fromMaybe "refactor" rpath + mexc <- findExecutable excPath + case mexc of + Just exc -> do + ver <- readVersion . tail <$> readProcess exc ["--version"] "" + pure $ if versionBranch ver >= [0,7,0,0] + then Right exc + else Left "Your version of refactor is too old, please upgrade to the latest version" + Nothing -> pure $ Left $ unlines + [ "Could not find 'refactor' executable" + , "Tried to find '" ++ excPath ++ "' on the PATH" + , "'refactor' is provided by the 'apply-refact' package and has to be installed" + , "" + ] + +runRefactoring :: FilePath -> FilePath -> FilePath -> [Extension] -> [Extension] -> String -> IO ExitCode +runRefactoring rpath fin hints enabled disabled opts = do + let args = [fin, "-v0"] ++ words opts ++ ["--refact-file", hints] + ++ [arg | e <- enabled, arg <- ["-X", show e]] + ++ [arg | e <- disabled, arg <- ["-X", "No" ++ show e]] + (_, _, _, phand) <- createProcess $ proc rpath args + try $ hSetBuffering stdin LineBuffering :: IO (Either IOException ()) + hSetBuffering stdout LineBuffering + -- Propagate the exit code from the spawn process + waitForProcess phand diff -Nru hlint-2.2.11/src/Report.hs hlint-3.1.6/src/Report.hs --- hlint-2.2.11/src/Report.hs 2020-02-09 21:16:43.000000000 +0000 +++ hlint-3.1.6/src/Report.hs 2020-05-13 11:33:34.000000000 +0000 @@ -8,11 +8,11 @@ import qualified Data.List.NonEmpty as NE import Data.Maybe import Data.Version -import HSE.All import Timing import Paths_hlint import HsColour import EmbedData +import qualified GHC.Util as GHC writeTemplate :: FilePath -> [(String,[String])] -> FilePath -> IO () @@ -28,7 +28,7 @@ where generateIds :: [String] -> [(String,Int)] -- sorted by name generateIds = map (NE.head &&& length) . NE.group -- must be already sorted - files = generateIds $ sort $ map (srcSpanFilename . ideaSpan) ideas + files = generateIds $ sort $ map (GHC.srcSpanFilename . ideaSpan) ideas hints = generateIds $ map hintName $ sortOn (negate . fromEnum . ideaSeverity &&& hintName) ideas hintName x = show (ideaSeverity x) ++ ": " ++ ideaHint x @@ -41,10 +41,10 @@ ("HINTS",list "hint" hints),("FILES",list "file" files)] content = concatMap (\i -> writeIdea (getClass i) i) ideas - getClass i = "hint" ++ f hints (hintName i) ++ " file" ++ f files (srcSpanFilename $ ideaSpan i) + getClass i = "hint" ++ f hints (hintName i) ++ " file" ++ f files (GHC.srcSpanFilename $ ideaSpan i) where f xs x = show $ fromJust $ findIndex ((==) x . fst) xs - list mode = zipWith f [0..] + list mode = zipWithFrom f 0 where f i (name,n) = "
  • " ++ escapeHTML name ++ " (" ++ show n ++ ")
  • " @@ -54,7 +54,7 @@ writeIdea :: String -> Idea -> [String] writeIdea cls Idea{..} = ["
    " - ,escapeHTML (showSrcLoc (getPointLoc ideaSpan) ++ ": " ++ show ideaSeverity ++ ": " ++ ideaHint) ++ "
    " + ,escapeHTML (GHC.showSrcSpan ideaSpan ++ ": " ++ show ideaSeverity ++ ": " ++ ideaHint) ++ "
    " ,"Found
    " ,hsColourHTML ideaFrom] ++ (case ideaTo of diff -Nru hlint-2.2.11/src/Test/All.hs hlint-3.1.6/src/Test/All.hs --- hlint-2.2.11/src/Test/All.hs 2019-08-07 19:52:47.000000000 +0000 +++ hlint-3.1.6/src/Test/All.hs 2020-06-24 11:09:26.000000000 +0000 @@ -1,5 +1,5 @@ {-# LANGUAGE RecordWildCards #-} -{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-} +{-# OPTIONS_GHC -Wno-incomplete-patterns #-} module Test.All(test) where @@ -8,7 +8,10 @@ import Control.Monad import Control.Monad.IO.Class import Data.Char +import Data.Either.Extra +import Data.Foldable import Data.List +import Data.Maybe import System.Directory import System.FilePath import Data.Functor @@ -17,27 +20,30 @@ import Config.Type import Config.Read import CmdLine -import HSE.All +import Refact import Hint.All -import Test.Util -import Test.InputOutput import Test.Annotations +import Test.InputOutput +import Test.Summary import Test.Translate +import Test.Util import System.IO.Extra +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable test :: Cmd -> ([String] -> IO ()) -> FilePath -> [FilePath] -> IO Int test CmdTest{..} main dataDir files = do - (failures, ideas) <- withBuffering stdout NoBuffering $ withTests $ do + rpath <- refactorPath (if cmdWithRefactor == "" then Nothing else Just cmdWithRefactor) + + (failures, (ideas, builtins)) <- withBuffering stdout NoBuffering $ withTests $ do hasSrc <- liftIO $ doesFileExist "hlint.cabal" - useSrc <- return $ hasSrc && null files - testFiles <- if files /= [] then return files else do + let useSrc = hasSrc && null files + testFiles <- if files /= [] then pure files else do xs <- liftIO $ getDirectoryContents dataDir - return [dataDir x | x <- xs, takeExtension x `elem` [".hs",".yml",".yaml"] - , not $ "HLint_" `isPrefixOf` takeBaseName x] + pure [dataDir x | x <- xs, takeExtension x `elem` [".yml",".yaml"]] testFiles <- liftIO $ forM testFiles $ \file -> do - hints <- readFilesConfig [(file, Nothing)] - return (file, hints ++ (if takeBaseName file /= "Test" then [] else map (Builtin . fst) builtinHints)) + hints <- readFilesConfig [(file, Nothing),("CommandLine.yaml", Just "- group: {name: testing, enabled: true}")] + pure (file, hints ++ (if takeBaseName file /= "Test" then [] else map (Builtin . fst) builtinHints)) let wrap msg act = do liftIO $ putStr (msg ++ " "); act; liftIO $ putStrLn "" liftIO $ putStrLn "Testing" @@ -46,11 +52,13 @@ config <- liftIO $ readFilesConfig [(".hlint.yaml",Nothing)] forM_ builtinHints $ \(name,_) -> do progress - testAnnotations (Builtin name : if name == "Restrict" then config else []) $ "src/Hint" name <.> "hs" + testAnnotations (Builtin name : if name == "Restrict" then config else []) + ("src/Hint" name <.> "hs") + (eitherToMaybe rpath) when useSrc $ wrap "Input/outputs" $ testInputOutput main wrap "Hint names" $ mapM_ (\x -> do progress; testNames $ snd x) testFiles - wrap "Hint annotations" $ forM_ testFiles $ \(file,h) -> do progress; testAnnotations h file + wrap "Hint annotations" $ forM_ testFiles $ \(file,h) -> do progress; testAnnotations h file (eitherToMaybe rpath) let hs = [h | (file, h) <- testFiles, takeFileName file /= "Test.hs"] when cmdTypeCheck $ wrap "Hint typechecking" $ progress >> testTypeCheck cmdDataDir cmdTempDir hs @@ -58,9 +66,13 @@ progress >> testQuickCheck cmdDataDir cmdTempDir hs when (null files && not hasSrc) $ liftIO $ putStrLn "Warning, couldn't find source code, so non-hint tests skipped" - getIdeas + (,) <$> getIdeas <*> getBuiltins whenLoud $ mapM_ print ideas - return failures + when cmdGenerateSummary $ writeFile "builtin.md" (genBuiltinSummaryMd builtins) + case rpath of + Left refactorNotFound -> putStrLn $ unlines [refactorNotFound, "Refactoring tests skipped"] + _ -> pure () + pure failures --------------------------------------------------------------------- @@ -69,7 +81,7 @@ -- Check all hints in the standard config files get sensible names testNames :: [Setting] -> Test () testNames hints = sequence_ - [ failed ["No name for the hint " ++ prettyPrint hintRuleLHS ++ " ==> " ++ prettyPrint hintRuleRHS] + [ failed ["No name for the hint " ++ unsafePrettyPrint hintRuleLHS ++ " ==> " ++ unsafePrettyPrint hintRuleRHS] | SettingMatchExp x@HintRule{..} <- hints, hintRuleName == defaultHintName] diff -Nru hlint-2.2.11/src/Test/Annotations.hs hlint-3.1.6/src/Test/Annotations.hs --- hlint-2.2.11/src/Test/Annotations.hs 2019-08-07 19:52:47.000000000 +0000 +++ hlint-3.1.6/src/Test/Annotations.hs 2020-06-24 11:09:26.000000000 +0000 @@ -1,51 +1,77 @@ -{-# LANGUAGE PatternGuards, RecordWildCards, ViewPatterns #-} +{-# LANGUAGE CPP, PatternGuards, RecordWildCards, ViewPatterns #-} -- | Check the annotations within source and hint files. module Test.Annotations(testAnnotations) where import Control.Exception.Extra -import Data.Tuple.Extra +import Control.Monad +import Control.Monad.IO.Class import Data.Char import Data.Either.Extra +import Data.Function +import Data.Functor import Data.List.Extra import Data.Maybe -import Control.Monad +import Data.Tuple.Extra +import System.Exit import System.FilePath -import Control.Monad.IO.Class -import Data.Function -import Data.Yaml +import System.IO.Extra +import GHC.All import qualified Data.ByteString.Char8 as BS import Config.Type import Idea import Apply -import HSE.All +import Extension +import Refact import Test.Util -import Data.Functor import Prelude import Config.Yaml +import FastString + +import GHC.Util +import SrcLoc +import Language.Haskell.GhclibParserEx.GHC.Utils.Outputable +#ifdef HS_YAML + +import Data.YAML.Aeson (decode1Strict) +import Data.YAML (Pos) +import Data.ByteString (ByteString) + +decodeEither' :: ByteString -> Either (Pos, String) ConfigYaml +decodeEither' = decode1Strict + +#else + +import Data.Yaml + +#endif -- Input, Output -- Output = Nothing, should not match -- Output = Just xs, should match xs -data TestCase = TestCase SrcLoc String (Maybe String) [Setting] deriving (Show) +data TestCase = TestCase SrcLoc Refactor String (Maybe String) [Setting] deriving (Show) -testAnnotations :: [Setting] -> FilePath -> Test () -testAnnotations setting file = do +data Refactor = TestRefactor | SkipRefactor deriving (Eq, Show) + +testAnnotations :: [Setting] -> FilePath -> Maybe FilePath -> Test () +testAnnotations setting file rpath = do tests <- liftIO $ parseTestFile file mapM_ f tests where - f (TestCase loc inp out additionalSettings) = do + f (TestCase loc refact inp out additionalSettings) = do ideas <- liftIO $ try_ $ do res <- applyHintFile defaultParseFlags (setting ++ additionalSettings) file $ Just inp evaluate $ length $ show res - return res + pure res + + when ("src/Hint" `isPrefixOf` file) $ mapM_ (mapM_ (addBuiltin inp)) ideas -- the hints from data/Test.hs are really fake hints we don't actually deploy -- so don't record them when (takeFileName file /= "Test.hs") $ - either (const $ return ()) addIdeas ideas + either (const $ pure ()) addIdeas ideas let good = case (out, ideas) of (Nothing, Right []) -> True @@ -54,18 +80,35 @@ let bad = [failed $ ["TEST FAILURE (" ++ show (either (const 1) length ideas) ++ " hints generated)" - ,"SRC: " ++ showSrcLoc loc + ,"SRC: " ++ unsafePrettyPrint loc ,"INPUT: " ++ inp] ++ - map ("OUTPUT: " ++) (either (return . show) (map show) ideas) ++ + map ("OUTPUT: " ++) (either (pure . show) (map show) ideas) ++ ["WANTED: " ++ fromMaybe "" out] | not good] ++ [failed ["TEST FAILURE (BAD LOCATION)" - ,"SRC: " ++ showSrcLoc loc + ,"SRC: " ++ unsafePrettyPrint loc ,"INPUT: " ++ inp ,"OUTPUT: " ++ show i] - | i@Idea{..} <- fromRight [] ideas, let SrcLoc{..} = getPointLoc ideaSpan, srcFilename == "" || srcLine == 0 || srcColumn == 0] - if null bad then passed else sequence_ bad + | i@Idea{..} <- fromRight [] ideas, let SrcLoc{..} = srcSpanStart ideaSpan, srcFilename == "" || srcLine == 0 || srcColumn == 0] + -- TODO: shouldn't these checks be == -1 instead? + + -- Skip refactoring test if the hlint test failed, or if the + -- test is annotated with @NoRefactor. + let skipRefactor = notNull bad || refact == SkipRefactor + badRefactor <- if skipRefactor then pure [] else liftIO $ do + refactorErr <- case ideas of + Right [] -> testRefactor rpath Nothing inp + Right [idea] -> testRefactor rpath (Just idea) inp + -- Skip refactoring test if there are multiple hints + _ -> pure [] + pure $ [failed $ + ["TEST FAILURE (BAD REFACTORING)" + ,"SRC: " ++ unsafePrettyPrint loc + ,"INPUT: " ++ inp] ++ refactorErr + | notNull refactorErr] + + if null bad && null badRefactor then passed else sequence_ (bad ++ badRefactor) match "???" _ = True match (word1 -> ("@Message",msg)) i = ideaHint i == msg @@ -81,7 +124,7 @@ parseTestFile :: FilePath -> IO [TestCase] parseTestFile file = -- we remove all leading # symbols since Yaml only lets us do comments that way - f Nothing . zip [1..] . map (\x -> fromMaybe x $ stripPrefix "# " x) . lines <$> readFile file + f Nothing TestRefactor . zipFrom 1 . map (dropPrefix "# ") . lines <$> readFile file where open :: String -> Maybe [Setting] open line @@ -96,20 +139,60 @@ shut :: String -> Bool shut = isPrefixOf "" - f :: Maybe [Setting] -> [(Int, String)] -> [TestCase] - f Nothing ((i,x):xs) = f (open x) xs - f (Just s) ((i,x):xs) - | shut x = f Nothing xs - | null x || "-- " `isPrefixOf` x = f (Just s) xs - | "\\" `isSuffixOf` x, (_,y):ys <- xs = f (Just s) $ (i,init x++"\n"++y):ys - | otherwise = parseTest file i x s : f (Just s) xs - f _ [] = [] + f :: Maybe [Setting] -> Refactor -> [(Int, String)] -> [TestCase] + f Nothing _ ((i,x):xs) = f (open x) TestRefactor xs + f (Just s) refact ((i,x):xs) + | shut x = f Nothing TestRefactor xs + | Just (x',_) <- stripInfix "@NoRefactor" x = + f (Just s) SkipRefactor ((i, trimEnd x' ++ ['\\' | "\\" `isSuffixOf` x]) : xs) + | null x || "-- " `isPrefixOf` x = f (Just s) refact xs + | Just x <- stripSuffix "\\" x, (_,y):ys <- xs = f (Just s) refact $ (i,x++"\n"++y):ys + | otherwise = parseTest refact file i x s : f (Just s) TestRefactor xs + f _ _ [] = [] -parseTest :: String -> Int -> String -> [Setting] -> TestCase -parseTest file i x = uncurry (TestCase (SrcLoc file i 0)) $ f x +parseTest :: Refactor -> String -> Int -> String -> [Setting] -> TestCase +parseTest refact file i x = uncurry (TestCase (mkSrcLoc (mkFastString file) i 0) refact) $ f x where f x | Just x <- stripPrefix "" x = first ("--"++) $ f x - f (' ':'-':'-':xs) | null xs || " " `isPrefixOf` xs = ("", Just $ dropWhile isSpace xs) + f (' ':'-':'-':xs) | null xs || " " `isPrefixOf` xs = ("", Just $ trimStart xs) f (x:xs) = first (x:) $ f xs f [] = ([], Nothing) + + +-- Returns an empty list if the refactoring test passes, otherwise +-- returns error messages. +testRefactor :: Maybe FilePath -> Maybe Idea -> String -> IO [String] +-- Skip refactoring test if the refactor binary is not found. +testRefactor Nothing _ _ = pure [] +-- Skip refactoring test if the hint has no suggestion (i.e., a parse error). +testRefactor _ (Just idea) _ | isNothing (ideaTo idea) = pure [] +testRefactor (Just rpath) midea inp = withTempFile $ \tempInp -> withTempFile $ \tempHints -> do + -- Note that we test the refactoring even if there are no suggestions, + -- as an extra test of apply-refact, on which we rely. + -- See https://github.com/ndmitchell/hlint/issues/958 for a discussion. + let refacts = map (show &&& ideaRefactoring) (maybeToList midea) + -- Ignores spaces and semicolons since apply-refact may change them. + process = filter (\c -> not (isSpace c) && c /= ';') + matched expected g actual = process expected `g` process actual + x `isProperSubsequenceOf` y = x /= y && x `isSubsequenceOf` y + writeFile tempInp inp + writeFile tempHints (show refacts) + exitCode <- runRefactoring rpath tempInp tempHints defaultExtensions [] "--inplace" + refactored <- readFile tempInp + pure $ case exitCode of + ExitFailure ec -> ["Refactoring failed: exit code " ++ show ec] + ExitSuccess -> case fmap ideaTo midea of + -- No hints. Refactoring should be a no-op. + Nothing | not (matched inp (==) refactored) -> + ["Expected refactor output: " ++ inp, "Actual: " ++ refactored] + -- The hint's suggested replacement is @Just ""@, which means the hint + -- suggests removing something from the input. The refactoring output + -- should be a proper subsequence of the input. + Just (Just "") | not (matched refactored isProperSubsequenceOf inp) -> + ["Refactor output is expected to be a proper subsequence of: " ++ inp, "Actual: " ++ refactored] + -- The hint has a suggested replacement. The suggested replacement + -- should be a substring of the refactoring output. + Just (Just to) | not (matched to isInfixOf refactored) -> + ["Refactor output is expected to contain: " ++ to, "Actual: " ++ refactored] + _ -> [] diff -Nru hlint-2.2.11/src/Test/InputOutput.hs hlint-3.1.6/src/Test/InputOutput.hs --- hlint-2.2.11/src/Test/InputOutput.hs 2019-04-16 11:02:29.000000000 +0000 +++ hlint-3.1.6/src/Test/InputOutput.hs 2020-03-16 09:57:58.000000000 +0000 @@ -24,10 +24,10 @@ testInputOutput :: ([String] -> IO ()) -> Test () testInputOutput main = do xs <- liftIO $ getDirectoryContents "tests" - xs <- return $ filter ((==) ".test" . takeExtension) xs + xs <- pure $ filter ((==) ".test" . takeExtension) xs forM_ xs $ \file -> do ios <- liftIO $ parseInputOutputs <$> readFile ("tests" file) - forM_ (zip [1..] ios) $ \(i,io@InputOutput{..}) -> do + forM_ (zipFrom 1 ios) $ \(i,io@InputOutput{..}) -> do progress liftIO $ forM_ files $ \(name,contents) -> do createDirectoryIfMissing True $ takeDirectory name @@ -71,7 +71,7 @@ handle (\(e::ExitCode) -> writeIORef code e) $ bracket getVerbosity setVerbosity $ const $ setVerbosity Normal >> main run code <- liftIO $ readIORef code - (want,got) <- return $ matchStarStar (lines output) got + (want,got) <- pure $ matchStarStar (lines output) got if maybe False (/= code) exit then failed @@ -96,7 +96,12 @@ -- | First string may have stars in it (the want) matchStar :: String -> String -> Bool matchStar ('*':xs) ys = any (matchStar xs) $ tails ys -matchStar (x:xs) (y:ys) = x == y && matchStar xs ys +matchStar ('/':x:xs) ('\\':'\\':ys) | x /= '/' = matchStar (x:xs) ys -- JSON escaped newlines +matchStar (x:xs) (y:ys) = eq x y && matchStar xs ys + where + -- allow path differences between Windows and Linux + eq '/' y = isPathSeparator y + eq x y = x == y matchStar [] [] = True matchStar _ _ = False diff -Nru hlint-2.2.11/src/Test/Proof.hs hlint-3.1.6/src/Test/Proof.hs --- hlint-2.2.11/src/Test/Proof.hs 2019-08-07 19:52:47.000000000 +0000 +++ hlint-3.1.6/src/Test/Proof.hs 2020-03-05 10:39:58.000000000 +0000 @@ -1,12 +1,21 @@ -{-# LANGUAGE RecordWildCards, PatternGuards, FlexibleContexts #-} -- | Check the coverage of the hints given a list of Isabelle theorems module Test.Proof(proof) where +import Config.Type +import Control.Exception.Extra + +proof :: [FilePath] -> [Setting] -> FilePath -> IO () +proof _ _ _ = errorIO "Test.Proof is disabled." + +{- + import Data.Tuple.Extra import Control.Applicative import Control.Monad import Control.Monad.Trans.State +import Language.Haskell.Exts.Util(paren, FreeVars, freeVars) +import qualified Data.Set as Set import Data.Char import Data.List.Extra import Data.Maybe @@ -143,8 +152,8 @@ f (ValidInstance cls var) x = evalState (transformM g x) True where g v@Var{} | v ~= var = do b <- get; put False - return $ if b then Paren an $ toNamed $ prettyPrint v ++ "::'a::" ++ cls ++ "_sym" else v - g v = return v :: State Bool Exp_ + pure $ if b then Paren an $ toNamed $ prettyPrint v ++ "::'a::" ++ cls ++ "_sym" else v + g v = pure v :: State Bool Exp_ f _ x = x relationship hintRuleNotes a b | any lazier hintRuleNotes = a ++ " \\ " ++ b @@ -184,3 +193,7 @@ pat x = prettyPrint x fresh x = head $ ("z":["v" ++ show i | i <- [1..]]) \\ vars x + +vars :: FreeVars a => a -> [String] +vars = Set.toList . Set.map prettyPrint . freeVars +-} diff -Nru hlint-2.2.11/src/Test/Summary.hs hlint-3.1.6/src/Test/Summary.hs --- hlint-2.2.11/src/Test/Summary.hs 1970-01-01 00:00:00.000000000 +0000 +++ hlint-3.1.6/src/Test/Summary.hs 2020-06-24 11:09:26.000000000 +0000 @@ -0,0 +1,58 @@ +{-# LANGUAGE RecordWildCards #-} + +-- | Generate a markdown that summarizes the builtin hints. +module Test.Summary (genBuiltinSummaryMd) where + +import qualified Data.Map as Map +import Config.Type +import Test.Util + +genBuiltinSummaryMd :: BuiltinSummary -> String +genBuiltinSummaryMd builtins = unlines $ + [ "# Built-in Hints" + , "" + , "This page is auto-generated from `cabal run hlint test -- --generate-summary`" + , "or `stack run hlint test -- --generate-summary`." + , "" + ] + ++ table builtins + +table :: BuiltinSummary -> [String] +table builtins = + [""] + ++ row ["", "", ""] + ++ Map.foldMapWithKey showHint builtins + ++ ["
    HintSeveritySupport Refactoring?
    "] + +row :: [String] -> [String] +row xs = [""] ++ xs ++ [""] + +-- | Render using if it is single-line, otherwise using
    .
    +haskell :: String -> [String]
    +haskell s
    +  | '\n' `elem` s = ["
    ", s, "
    "] + | otherwise = ["", s, "", "
    "] + +showHint :: (String, Severity, Bool) -> BuiltinEx -> [String] +showHint (hint, sev, refact) BuiltinEx{..} = row1 ++ row2 + where + row1 = row + [ "" ++ hint ++ "" + , "" ++ show sev ++ "" + , "" ++ if refact then "Yes" else "No" ++ "" + ] + row2 = row example + example = + [ "" + , "Example:" + ] + ++ haskell builtinInp + ++ ["Found:"] + ++ haskell builtinFrom + ++ ["Suggestion:"] + ++ haskell to + ++ [""] + to = case builtinTo of + Nothing -> "" + Just "" -> "Perhaps you should remove it." + Just s -> s diff -Nru hlint-2.2.11/src/Test/Translate.hs hlint-3.1.6/src/Test/Translate.hs --- hlint-2.2.11/src/Test/Translate.hs 2019-11-27 22:26:42.000000000 +0000 +++ hlint-3.1.6/src/Test/Translate.hs 2020-03-05 10:40:35.000000000 +0000 @@ -2,6 +2,19 @@ -- | Translate the hints to Haskell and run with GHC. module Test.Translate(testTypeCheck, testQuickCheck) where +import Config.Type +import Control.Exception.Extra +import Control.Monad.IO.Class +import Test.Util + +testTypeCheck :: FilePath -> FilePath -> [[Setting]] -> Test () +testTypeCheck _ _ _ = liftIO $ errorIO "Test.Translate is disabled." + +-- | Given a set of hints, do all the HintRule hints satisfy QuickCheck +testQuickCheck :: FilePath -> FilePath -> [[Setting]] -> Test () +testQuickCheck _ _ _ = liftIO $ errorIO "Test.Translate is disabled." + +{- import Control.Monad import Control.Monad.IO.Class import Data.List.Extra @@ -10,6 +23,8 @@ import System.Process import System.Exit import System.FilePath +import Language.Haskell.Exts.Util(FreeVars, freeVars) +import qualified Data.Set as Set import Config.Type import HSE.All @@ -19,10 +34,10 @@ runMains :: FilePath -> FilePath -> [String] -> Test () runMains datadir tmpdir xs = do res <- liftIO $ (if tmpdir == "" then withTempDir else ($ tmpdir)) $ \dir -> do - ms <- forM (zip [1..] xs) $ \(i,x) -> do + ms <- forM (zipFrom 1 xs) $ \(i,x) -> do let m = "I" ++ show i writeFile (dir m <.> "hs") $ replace "module Main" ("module " ++ m) x - return m + pure m writeFile (dir "Main.hs") $ unlines $ ["import qualified " ++ m | m <- ms] ++ ["main = do"] ++ @@ -46,13 +61,15 @@ ["{-# LANGUAGE NoMonomorphismRestriction, ExtendedDefaultRules, ScopedTypeVariables, DeriveDataTypeable #-}" ,"{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}" ,"module Main(main) where"] ++ - concat [map (prettyPrint . hackImport) $ scopeImports $ hintRuleScope x | x <- take 1 xs] ++ + -- concat [map (prettyPrint . hackImport) $ scopeImports $ hintRuleScope x | x <- take 1 xs] ++ f xs + {- -- Hack around haskell98 not being compatible with base anymore hackImport i@ImportDecl{importAs=Just a,importModule=b} | prettyPrint b `elem` words "Maybe List Monad IO Char" = i{importAs=Just b,importModule=a} hackImport i = i + -} --------------------------------------------------------------------- @@ -61,10 +78,10 @@ toTypeCheck :: [HintRule] -> [String] toTypeCheck hints = ["import HLint_TypeCheck hiding(main)" - ,"main = return ()"] ++ + ,"main = pure ()"] ++ ["{-# LINE " ++ show (startLine $ ann rhs) ++ " " ++ show (fileName $ ann rhs) ++ " #-}\n" ++ prettyPrint (PatBind an (toNamed $ "test" ++ show i) bod Nothing) - | (i, HintRule _ _ _ lhs rhs side _notes _ghcScope _ghcLhs _ghcRhs _ghcSide) <- zip [1..] hints, "noTypeCheck" `notElem` vars (maybeToList side) + | (i, HintRule _ _ lhs rhs side _notes _ghcScope _ghcLhs _ghcRhs _ghcSide) <- zipFrom 1 hints, "noTypeCheck" `notElem` vars (maybeToList side) , let vs = map toNamed $ nubOrd $ filter isUnifyVar $ vars lhs ++ vars rhs , let inner = InfixApp an (Paren an lhs) (toNamed "==>") (Paren an rhs) , let bod = UnGuardedRhs an $ if null vs then inner else Lambda an vs inner] @@ -88,7 +105,7 @@ Let an (BDecls an [PatBind an (toNamed "t") (UnGuardedRhs an bod) Nothing]) $ (toNamed "test" `app` str (fileName $ ann rhs) `app` int (startLine $ ann rhs) `app` str (prettyPrint lhs ++ " ==> " ++ prettyPrint rhs)) `app` toNamed "t" - | (i, HintRule _ _ _ lhs rhs side note _ghcScope _ghcLhs _ghcRhs _ghcSide) <- zip [1..] hints, "noQuickCheck" `notElem` vars (maybeToList side) + | (i, HintRule _ _ lhs rhs side note _ghcScope _ghcLhs _ghcRhs _ghcSide) <- zipFrom 1 hints, "noQuickCheck" `notElem` vars (maybeToList side) , let vs = map (restrict side) $ nubOrd $ filter isUnifyVar $ vars lhs ++ vars rhs , let op = if any isRemovesError note then "?==>" else "==>" , let inner = InfixApp an (Paren an lhs) (toNamed op) (Paren an rhs) @@ -104,3 +121,7 @@ isRemovesError :: Note -> Bool isRemovesError RemovesError{} = True isRemovesError _ = False + +vars :: FreeVars a => a -> [String] +vars = Set.toList . Set.map prettyPrint . freeVars +-} diff -Nru hlint-2.2.11/src/Test/Util.hs hlint-3.1.6/src/Test/Util.hs --- hlint-2.2.11/src/Test/Util.hs 2019-04-16 15:19:57.000000000 +0000 +++ hlint-3.1.6/src/Test/Util.hs 2020-06-24 11:09:26.000000000 +0000 @@ -2,8 +2,9 @@ module Test.Util( Test, withTests, - tested, passed, failed, progress, - addIdeas, getIdeas + passed, failed, progress, + addIdeas, getIdeas, + BuiltinSummary, BuiltinEx(..), addBuiltin, getBuiltins, ) where import Idea @@ -11,12 +12,25 @@ import Control.Monad.Trans.Reader import Control.Monad.IO.Class import Data.IORef - +import Data.List.Extra +import Data.Map (Map) +import qualified Data.Map.Strict as Map + +-- | A map from (hint name, hint severity, does hint support refactoring) to an example. +type BuiltinSummary = Map (String, Severity, Bool) BuiltinEx + +data BuiltinEx = BuiltinEx + { builtinInp :: !String + , builtinFrom :: !String + , builtinTo :: !(Maybe String) + } data S = S {failures :: !Int ,total :: !Int ,ideas :: [[Idea]] + ,builtinHints :: BuiltinSummary + -- ^ A summary of builtin hints } newtype Test a = Test (ReaderT (IORef S) IO a) @@ -25,14 +39,14 @@ -- | Returns the number of failing tests. withTests :: Test a -> IO (Int, a) withTests (Test act) = do - ref <- newIORef $ S 0 0 [] + ref <- newIORef $ S 0 0 [] Map.empty res <- runReaderT act ref S{..} <- readIORef ref putStrLn "" putStrLn $ if failures == 0 then "Tests passed (" ++ show total ++ ")" else "Tests failed (" ++ show failures ++ " of " ++ show total ++ ")" - return (failures, res) + pure (failures, res) addIdeas :: [Idea] -> Test () addIdeas xs = do @@ -44,6 +58,21 @@ ref <- Test ask liftIO $ concat . reverse . ideas <$> readIORef ref +addBuiltin :: String -> Idea -> Test () +addBuiltin inp idea@Idea{..} = unless ("Parse error" `isPrefixOf` ideaHint) $ do + ref <- Test ask + liftIO $ modifyIORef' ref $ \s -> + let k = (ideaHint, ideaSeverity, notNull ideaRefactoring) + v = BuiltinEx inp ideaFrom ideaTo + -- Do not insert if the key already exists in the map. This has the effect + -- of picking the first test case of a hint as the example in the summary. + in s{builtinHints = Map.insertWith (curry snd) k v (builtinHints s)} + +getBuiltins :: Test BuiltinSummary +getBuiltins = do + ref <- Test ask + liftIO $ builtinHints <$> readIORef ref + progress :: Test () progress = liftIO $ putChar '.' @@ -57,6 +86,3 @@ unless (null xs) $ liftIO $ putStrLn $ unlines $ "" : xs ref <- Test ask liftIO $ modifyIORef' ref $ \s -> s{total=total s+1, failures=failures s+1} - -tested :: Bool -> Test () -tested b = if b then passed else failed [] diff -Nru hlint-2.2.11/src/Timing.hs hlint-3.1.6/src/Timing.hs --- hlint-2.2.11/src/Timing.hs 2019-02-27 11:44:31.000000000 +0000 +++ hlint-3.1.6/src/Timing.hs 2020-05-31 22:44:28.000000000 +0000 @@ -41,9 +41,9 @@ let quiet = c == "Hint" unless quiet $ whenLoud $ putStr $ "Performing " ++ c ++ " of " ++ i ++ "... " (time, x) <- duration x - atomicModifyIORef' timings $ \mp -> (Map.insertWith (+) (c, i) time mp, ()) + atomicModifyIORef'_ timings $ Map.insertWith (+) (c, i) time unless quiet $ whenLoud $ putStrLn $ "took " ++ showDuration time - return x + pure x startTimings :: IO () startTimings = do diff -Nru hlint-2.2.11/src/Util.hs hlint-3.1.6/src/Util.hs --- hlint-2.2.11/src/Util.hs 2020-01-27 17:28:56.000000000 +0000 +++ hlint-3.1.6/src/Util.hs 2020-06-14 18:45:05.000000000 +0000 @@ -1,22 +1,18 @@ {-# LANGUAGE ExistentialQuantification, Rank2Types #-} module Util( - parseExtensions, - configExtensions, forceList, gzip, universeParentBi, exitMessage, exitMessageImpure, getContentsUTF8 ) where -import Data.List import System.Exit import System.IO import System.IO.Unsafe import Unsafe.Coerce import Data.Data -import Data.Generics.Uniplate.Operations -import Language.Haskell.Exts.Extension +import Data.Generics.Uniplate.DataOnly --------------------------------------------------------------------- @@ -60,37 +56,11 @@ --------------------------------------------------------------------- -- DATA.GENERICS.UNIPLATE.OPERATIONS -universeParent :: Uniplate a => a -> [(Maybe a, a)] +universeParent :: Data a => a -> [(Maybe a, a)] universeParent x = (Nothing,x) : f x where - f :: Uniplate a => a -> [(Maybe a, a)] + f :: Data a => a -> [(Maybe a, a)] f x = concat [(Just x, y) : f y | y <- children x] -universeParentBi :: Biplate a b => a -> [(Maybe b, b)] +universeParentBi :: (Data a, Data b) => a -> [(Maybe b, b)] universeParentBi = concatMap universeParent . childrenBi - - ---------------------------------------------------------------------- --- LANGUAGE.HASKELL.EXTS.EXTENSION - --- | Extensions we turn on by default when parsing. Aim to parse as many files as we can. -parseExtensions :: [Extension] -parseExtensions = [e | e@EnableExtension{} <- knownExtensions] \\ map EnableExtension badExtensions - --- | Extensions we turn on when reading config files, don't have to deal with the whole world --- of variations - in particular, we might require spaces in some places. -configExtensions :: [Extension] -configExtensions = [e | e@EnableExtension{} <- knownExtensions] \\ map EnableExtension reallyBadExtensions - -badExtensions = reallyBadExtensions ++ - [Arrows -- steals proc - ,UnboxedTuples, UnboxedSums -- breaks (#) lens operator - ,QuasiQuotes -- breaks [x| ...], making whitespace free list comps break - ,DoRec, RecursiveDo -- breaks rec - ,TypeApplications -- HSE fails on @ patterns - ] - -reallyBadExtensions = - [TransformListComp -- steals the group keyword - ,XmlSyntax, RegularPatterns -- steals a-b and < operators - ]