Skip to content

Commit 3967b6d

Browse files
committed
Fix: don't suggest "Use :" when OverloadedLists is enabled (fixes #1602)
With OverloadedLists, `\x -> [x]` and `(: [])` have different types: the former produces any IsList instance (Vector a, Set a, NonEmpty a, ...) while the latter specialises to [a]. Applying the suggestion produces code that no longer type-checks. Move the hint from data/hlint.yaml into a code-based hint in Hint/List.hs, gated on `not overloadedListsOn` (same pattern as #114 for "Use list literal" and #1674 for "Use unwords"). The hint still fires for `\x -> [x]` when the module does not enable OverloadedLists, and is suppressed per-file when it does. Tests added to the TEST block in Hint/List.hs. Self-test: 966 tests, all pass.
1 parent 086bf44 commit 3967b6d

2 files changed

Lines changed: 20 additions & 1 deletion

File tree

data/hlint.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@
211211
- hint: {lhs: any (const True) x, rhs: not (null x), name: Use null}
212212
- hint: {lhs: length x /= 0, rhs: not (null x), note: IncreasesLaziness, name: Use null}
213213
- hint: {lhs: 0 /= length x, rhs: not (null x), note: IncreasesLaziness, name: Use null}
214-
- hint: {lhs: "\\x -> [x]", rhs: "(:[])", name: "Use :"}
214+
# Moved to code-based hint in Hint/List.hs to skip under OverloadedLists (issue #1602)
215+
# - hint: {lhs: "\\x -> [x]", rhs: "(:[])", name: "Use :"}
215216
- hint: {lhs: map f (zip x y), rhs: zipWith (curry f) x y, side: not (isApp f)}
216217
- hint: {lhs: "map f (fromMaybe [] x)", rhs: "maybe [] (map f) x"}
217218
- hint: {lhs: "concatMap f (fromMaybe [] x)", rhs: "maybe [] (concatMap f) x"}

src/Hint/List.hs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ foo = [_ | x <- _, let _ = A{x}]
3737
issue1039 = foo (map f [1 | _ <- []]) -- [f 1 | _ <- []]
3838
{-# LANGUAGE OverloadedLists #-} \
3939
issue114 = True:[]
40+
{-# LANGUAGE OverloadedLists #-} \
41+
no_issue1602 = \x -> [x]
42+
yes_issue1602 = \x -> [x] -- (:[])
4043
</TEST>
4144
-}
4245

@@ -188,6 +191,7 @@ checks overloadedListsOn = let (*) = (,) in drop1 -- see #174
188191
, "Use :" * useCons
189192
]
190193
<> ["Use list literal" * useList | not overloadedListsOn ] -- see #114
194+
<> ["Use :" * useConsLambda | not overloadedListsOn ] -- see #1602
191195

192196
pchecks :: [(String, LPat GhcPs -> Maybe (LPat GhcPs, [(String, R.SrcSpan)], String))]
193197
pchecks = let (*) = (,) in drop1 -- see #174
@@ -263,6 +267,20 @@ useCons False (view -> App2 op x y) | varToStr op == "++"
263267
gen x = noLocA . OpApp noExtField x (noLocA (HsVar noExtField (noLocA consDataCon_RDR)))
264268
useCons _ _ = Nothing
265269

270+
-- Suggest `(: [])` for `\x -> [x]`. Gated on `not overloadedListsOn`
271+
-- because under OverloadedLists the literal `[x]` can be polymorphic
272+
-- (e.g. Vector a, Set a, NonEmpty a), whereas `(: [])` specialises
273+
-- to the list type and the rewrite produces code that no longer
274+
-- type-checks (issue #1602).
275+
useConsLambda :: Bool -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [(String, R.SrcSpan)], String)
276+
useConsLambda _ (SimpleLambda [view -> PVar_ x] (L _ (ExplicitList _ [L _ (HsVar _ (L _ y))])))
277+
| occNameStr y == x =
278+
let consSection = noLocA (HsPar noAnn (noLocA (SectionR noExtField
279+
(noLocA (HsVar noExtField (noLocA consDataCon_RDR)))
280+
(noLocA (ExplicitList noAnn [])))))
281+
in Just (consSection, [], "(: [])")
282+
useConsLambda _ _ = Nothing
283+
266284
typeListChar :: LHsType GhcPs
267285
typeListChar =
268286
noLocA $ HsListTy noAnn

0 commit comments

Comments
 (0)