Skip to content

Commit d2bb0d9

Browse files
Don't suggest destruct actions for already-destructed terms (#1715)
* SYB improvemnets * Case splits require a space when exactprinting * Add the new SYB module to cabal * Use a typeclass for PatCompat * Change the type of disallowing to use a Set * SYB adds a stupid instance that crashes silently.... * Notify the judgment about already-destructed terms * Remove specialized to/from patcompat * Only allow destructing the local hypothesis * ext1Q doesn't do universal quantification SAD * Add new provider tests * Revert changes to ExactPrint that didn't help * Tidy and haddock * Polymorphic SrcSpan-aware mkQ SYB traversal * Add a note on why unsafeCoerce is safe * Fix a terrible name Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent f8f7c1d commit d2bb0d9

File tree

14 files changed

+180
-39
lines changed

14 files changed

+180
-39
lines changed

ghcide/src/Development/IDE/GHC/ExactPrint.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ needsParensSpace SectionL{} = (All False, All False)
196196
needsParensSpace SectionR{} = (All False, All False)
197197
needsParensSpace ExplicitTuple{} = (All False, All False)
198198
needsParensSpace ExplicitSum{} = (All False, All False)
199-
needsParensSpace HsCase{} = (All False, All False)
199+
needsParensSpace HsCase{} = (All False, All True)
200200
needsParensSpace HsIf{} = (All False, All False)
201201
needsParensSpace HsMultiIf{} = (All False, All False)
202202
needsParensSpace HsLet{} = (All False, All True)
@@ -337,7 +337,7 @@ genericGraftWithLargestM proxy dst trans = Graft $ \dflags ->
337337
-- 'everywhereM' or friends.
338338
--
339339
-- The 'Int' argument is the index in the list being bound.
340-
mkBindListT :: forall b m. (Typeable b, Data b, Monad m) => (Int -> b -> m [b]) -> GenericM m
340+
mkBindListT :: forall b m. (Data b, Monad m) => (Int -> b -> m [b]) -> GenericM m
341341
mkBindListT f = mkM $ fmap join . traverse (uncurry f) . zip [0..]
342342

343343

plugins/hls-tactics-plugin/hls-tactics-plugin.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ library
3737
Wingman.FeatureSet
3838
Wingman.GHC
3939
Wingman.Judgements
40+
Wingman.Judgements.SYB
4041
Wingman.Judgements.Theta
4142
Wingman.KnownStrategies
4243
Wingman.KnownStrategies.QuickCheck

plugins/hls-tactics-plugin/src/Wingman/Auto.hs

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Wingman.Auto where
22

33
import Control.Monad.State (gets)
4+
import qualified Data.Set as S
45
import Refinery.Tactic
56
import Wingman.Context
67
import Wingman.Judgements
@@ -24,5 +25,6 @@ auto = do
2425
. tracing "auto"
2526
. localTactic (auto' 4)
2627
. disallowing RecursiveCall
28+
. S.fromList
2729
$ fmap fst current
2830

plugins/hls-tactics-plugin/src/Wingman/CaseSplit.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ agdaSplit :: AgdaMatch -> [AgdaMatch]
3333
agdaSplit (AgdaMatch pats (Case (HsVar _ (L _ var)) matches)) = do
3434
(pat, body) <- matches
3535
-- TODO(sandy): use an at pattern if necessary
36-
pure $ AgdaMatch (rewriteVarPat var pat pats) body
36+
pure $ AgdaMatch (rewriteVarPat var pat pats) $ unLoc body
3737
agdaSplit x = [x]
3838

3939

plugins/hls-tactics-plugin/src/Wingman/CodeGen.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ destruct' f hi jdg = do
160160
f
161161
(Just term)
162162
(hi_type hi)
163-
$ disallowing AlreadyDestructed [term] jdg
163+
$ disallowing AlreadyDestructed (S.singleton term) jdg
164164
pure $ ext
165165
& #syn_trace %~ rose ("destruct " <> show term) . pure
166166
& #syn_used_vals %~ S.insert term

plugins/hls-tactics-plugin/src/Wingman/GHC.hs

+29-22
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import DataCon
1919
import Development.IDE (HscEnvEq (hscEnv))
2020
import Development.IDE.Core.Compile (lookupName)
2121
import Development.IDE.GHC.Compat
22-
import GHC.SourceGen (case', lambda, match)
22+
import GHC.SourceGen (lambda)
2323
import Generics.SYB (Data, everything, everywhere, listify, mkQ, mkT)
2424
import GhcPlugins (extractModule, GlobalRdrElt (gre_name))
2525
import OccName
@@ -188,8 +188,8 @@ allOccNames = everything (<>) $ mkQ mempty $ \case
188188
pattern AMatch :: HsMatchContext (NameOrRdrName (IdP GhcPs)) -> [Pat GhcPs] -> HsExpr GhcPs -> Match GhcPs (LHsExpr GhcPs)
189189
pattern AMatch ctx pats body <-
190190
Match { m_ctxt = ctx
191-
, m_pats = fmap fromPatCompatPs -> pats
192-
, m_grhss = UnguardedRHSs body
191+
, m_pats = fmap fromPatCompat -> pats
192+
, m_grhss = UnguardedRHSs (unLoc -> body)
193193
}
194194

195195

@@ -207,23 +207,23 @@ pattern Lambda pats body <-
207207

208208
------------------------------------------------------------------------------
209209
-- | A GRHS that caontains no guards.
210-
pattern UnguardedRHSs :: HsExpr GhcPs -> GRHSs GhcPs (LHsExpr GhcPs)
210+
pattern UnguardedRHSs :: LHsExpr p -> GRHSs p (LHsExpr p)
211211
pattern UnguardedRHSs body <-
212-
GRHSs {grhssGRHSs = [L _ (GRHS _ [] (L _ body))]}
212+
GRHSs {grhssGRHSs = [L _ (GRHS _ [] body)]}
213213

214214

215215
------------------------------------------------------------------------------
216216
-- | A match with a single pattern. Case matches are always 'SinglePatMatch'es.
217-
pattern SinglePatMatch :: Pat GhcPs -> HsExpr GhcPs -> Match GhcPs (LHsExpr GhcPs)
217+
pattern SinglePatMatch :: PatCompattable p => Pat p -> LHsExpr p -> Match p (LHsExpr p)
218218
pattern SinglePatMatch pat body <-
219-
Match { m_pats = [fromPatCompatPs -> pat]
219+
Match { m_pats = [fromPatCompat -> pat]
220220
, m_grhss = UnguardedRHSs body
221221
}
222222

223223

224224
------------------------------------------------------------------------------
225225
-- | Helper function for defining the 'Case' pattern.
226-
unpackMatches :: [Match GhcPs (LHsExpr GhcPs)] -> Maybe [(Pat GhcPs, HsExpr GhcPs)]
226+
unpackMatches :: PatCompattable p => [Match p (LHsExpr p)] -> Maybe [(Pat p, LHsExpr p)]
227227
unpackMatches [] = Just []
228228
unpackMatches (SinglePatMatch pat body : matches) =
229229
(:) <$> pure (pat, body) <*> unpackMatches matches
@@ -232,13 +232,10 @@ unpackMatches _ = Nothing
232232

233233
------------------------------------------------------------------------------
234234
-- | A pattern over the otherwise (extremely) messy AST for lambdas.
235-
pattern Case :: HsExpr GhcPs -> [(Pat GhcPs, HsExpr GhcPs)] -> HsExpr GhcPs
235+
pattern Case :: PatCompattable p => HsExpr p -> [(Pat p, LHsExpr p)] -> HsExpr p
236236
pattern Case scrutinee matches <-
237237
HsCase _ (L _ scrutinee)
238238
(MG {mg_alts = L _ (fmap unLoc -> unpackMatches -> Just matches)})
239-
where
240-
Case scrutinee matches =
241-
case' scrutinee $ fmap (\(pat, body) -> match [pat] body) matches
242239

243240

244241
------------------------------------------------------------------------------
@@ -253,20 +250,30 @@ lambdaCaseable (splitFunTy_maybe -> Just (arg, res))
253250
= Just $ isJust $ algebraicTyCon res
254251
lambdaCaseable _ = Nothing
255252

256-
-- It's hard to generalize over these since weird type families are involved.
257-
fromPatCompatTc :: PatCompat GhcTc -> Pat GhcTc
258-
toPatCompatTc :: Pat GhcTc -> PatCompat GhcTc
259-
fromPatCompatPs :: PatCompat GhcPs -> Pat GhcPs
253+
class PatCompattable p where
254+
fromPatCompat :: PatCompat p -> Pat p
255+
toPatCompat :: Pat p -> PatCompat p
256+
260257
#if __GLASGOW_HASKELL__ == 808
258+
instance PatCompattable GhcTc where
259+
fromPatCompat = id
260+
toPatCompat = id
261+
262+
instance PatCompattable GhcPs where
263+
fromPatCompat = id
264+
toPatCompat = id
265+
261266
type PatCompat pass = Pat pass
262-
fromPatCompatTc = id
263-
fromPatCompatPs = id
264-
toPatCompatTc = id
265267
#else
268+
instance PatCompattable GhcTc where
269+
fromPatCompat = unLoc
270+
toPatCompat = noLoc
271+
272+
instance PatCompattable GhcPs where
273+
fromPatCompat = unLoc
274+
toPatCompat = noLoc
275+
266276
type PatCompat pass = LPat pass
267-
fromPatCompatTc = unLoc
268-
fromPatCompatPs = unLoc
269-
toPatCompatTc = noLoc
270277
#endif
271278

272279
------------------------------------------------------------------------------

plugins/hls-tactics-plugin/src/Wingman/Judgements.hs

+4-4
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ filterAncestry
131131
-> Judgement
132132
-> Judgement
133133
filterAncestry ancestry reason jdg =
134-
disallowing reason (M.keys $ M.filterWithKey go $ hyByName $ jHypothesis jdg) jdg
134+
disallowing reason (M.keysSet $ M.filterWithKey go $ hyByName $ jHypothesis jdg) jdg
135135
where
136136
go name _
137137
= not
@@ -198,7 +198,7 @@ filterSameTypeFromOtherPositions dcon pos jdg =
198198
to_remove =
199199
M.filter (flip S.member tys . hi_type) (hyByName $ jHypothesis jdg)
200200
M.\\ hy
201-
in disallowing Shadowed (M.keys to_remove) jdg
201+
in disallowing Shadowed (M.keysSet to_remove) jdg
202202

203203

204204
------------------------------------------------------------------------------
@@ -258,8 +258,8 @@ patternHypothesis scrutinee dc jdg
258258
------------------------------------------------------------------------------
259259
-- | Prevent some occnames from being used in the hypothesis. This will hide
260260
-- them from 'jHypothesis', but not from 'jEntireHypothesis'.
261-
disallowing :: DisallowReason -> [OccName] -> Judgement' a -> Judgement' a
262-
disallowing reason (S.fromList -> ns) =
261+
disallowing :: DisallowReason -> S.Set OccName -> Judgement' a -> Judgement' a
262+
disallowing reason ns =
263263
field @"_jHypothesis" %~ (\z -> Hypothesis . flip fmap (unHypothesis z) $ \hi ->
264264
case S.member (hi_name hi) ns of
265265
True -> overProvenance (DisallowedPrv reason) hi
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
{-# LANGUAGE AllowAmbiguousTypes #-}
2+
{-# LANGUAGE RankNTypes #-}
3+
4+
-- | Custom SYB traversals
5+
module Wingman.Judgements.SYB where
6+
7+
import Data.Foldable (foldl')
8+
import Data.Generics hiding (typeRep)
9+
import Development.IDE.GHC.Compat
10+
import GHC.Exts (Any)
11+
import Type.Reflection
12+
import Unsafe.Coerce (unsafeCoerce)
13+
14+
15+
------------------------------------------------------------------------------
16+
-- | Like 'everything', but only looks inside 'Located' terms that contain the
17+
-- given 'SrcSpan'.
18+
everythingContaining
19+
:: forall r
20+
. Monoid r
21+
=> SrcSpan
22+
-> GenericQ r
23+
-> GenericQ r
24+
everythingContaining dst f = go
25+
where
26+
go :: GenericQ r
27+
go x =
28+
case genericIsSubspan dst x of
29+
Just False -> mempty
30+
_ -> foldl' (<>) (f x) (gmapQ go x)
31+
32+
33+
------------------------------------------------------------------------------
34+
-- | Helper function for implementing 'everythingWithin'
35+
--
36+
-- NOTE(sandy): Subtly broken. In an ideal world, this function shuld return
37+
-- @Just False@ for nodes of /any type/ which do not contain the span. But if
38+
-- this functionality exists anywhere within the SYB machinery, I have yet to
39+
-- find it.
40+
genericIsSubspan
41+
:: SrcSpan
42+
-> GenericQ (Maybe Bool)
43+
genericIsSubspan dst = mkQ1 (L noSrcSpan ()) Nothing $ \case
44+
L span _ -> Just $ dst `isSubspanOf` span
45+
46+
47+
------------------------------------------------------------------------------
48+
-- | Like 'mkQ', but allows for polymorphic instantiation of its specific case.
49+
-- This instantation matches whenever the dynamic value has the same
50+
-- constructor as the proxy @f ()@ value.
51+
mkQ1 :: forall a r f
52+
. (Data a, Data (f ()))
53+
=> f () -- ^ Polymorphic constructor to match on
54+
-> r -- ^ Default value
55+
-> (forall b. f b -> r) -- ^ Polymorphic match
56+
-> a
57+
-> r
58+
mkQ1 proxy r br a =
59+
case l_con == a_con && sameTypeModuloLastApp @a @(f ()) of
60+
-- We have proven that the two values share the same constructor, and
61+
-- that they have the same type if you ignore the final application.
62+
-- Therefore, it is safe to coerce @a@ to @f b@, since @br@ is universal
63+
-- over @b@ and can't inspect it.
64+
True -> br $ unsafeCoerce @_ @(f Any) a
65+
False -> r
66+
where
67+
l_con = toConstr proxy
68+
a_con = toConstr a
69+
70+
71+
------------------------------------------------------------------------------
72+
-- | Given @a ~ f1 a1@ and @b ~ f2 b2@, returns true if @f1 ~ f2@.
73+
sameTypeModuloLastApp :: forall a b. (Typeable a, Typeable b) => Bool
74+
sameTypeModuloLastApp =
75+
let tyrep1 = typeRep @a
76+
tyrep2 = typeRep @b
77+
in case (tyrep1 , tyrep2) of
78+
(App a _, App b _) ->
79+
case eqTypeRep a b of
80+
Just HRefl -> True
81+
Nothing -> False
82+
_ -> False
83+

plugins/hls-tactics-plugin/src/Wingman/LanguageServer.hs

+25-6
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ import Data.IORef (readIORef)
1818
import qualified Data.Map as M
1919
import Data.Maybe
2020
import Data.Monoid
21-
import qualified Data.Set as S
21+
import Data.Set (Set)
22+
import qualified Data.Set as S
2223
import qualified Data.Text as T
2324
import Data.Traversable
2425
import Development.IDE (getFilesOfInterest, ShowDiagnostic (ShowDiag), srcSpanToRange)
2526
import Development.IDE (hscEnv)
26-
import Development.IDE.Core.PositionMapping
2727
import Development.IDE.Core.RuleTypes
2828
import Development.IDE.Core.Rules (usePropertyAction)
2929
import Development.IDE.Core.Service (runAction)
@@ -52,6 +52,7 @@ import Wingman.Context
5252
import Wingman.FeatureSet
5353
import Wingman.GHC
5454
import Wingman.Judgements
55+
import Wingman.Judgements.SYB (everythingContaining)
5556
import Wingman.Judgements.Theta
5657
import Wingman.Range
5758
import Wingman.Types
@@ -221,20 +222,38 @@ mkJudgementAndContext features g (TrackedStale binds bmap) rss (TrackedStale tcg
221222
kt
222223
evidence
223224
top_provs = getRhsPosVals tcg_rss tcs
225+
already_destructed = getAlreadyDestructed (fmap RealSrcSpan tcg_rss) tcs
224226
local_hy = spliceProvenance top_provs
225227
$ hypothesisFromBindings binds_rss binds
226228
evidence = getEvidenceAtHole (fmap RealSrcSpan tcg_rss) tcs
227229
cls_hy = foldMap evidenceToHypothesis evidence
228230
subst = ts_unifier $ appEndo (foldMap (Endo . evidenceToSubst) evidence) defaultTacticState
229-
pure
230-
( fmap (CType . substTyAddInScope subst . unCType) $ mkFirstJudgement
231+
pure $
232+
( disallowing AlreadyDestructed already_destructed
233+
$ fmap (CType . substTyAddInScope subst . unCType) $ mkFirstJudgement
231234
(local_hy <> cls_hy)
232235
(isRhsHole tcg_rss tcs)
233236
g
234237
, ctx
235238
)
236239

237240

241+
------------------------------------------------------------------------------
242+
-- | Determine which bindings have already been destructed by the location of
243+
-- the hole.
244+
getAlreadyDestructed
245+
:: Tracked age SrcSpan
246+
-> Tracked age (LHsBinds GhcTc)
247+
-> Set OccName
248+
getAlreadyDestructed (unTrack -> span) (unTrack -> binds) =
249+
everythingContaining span
250+
(mkQ mempty $ \case
251+
Case (HsVar _ (L _ (occName -> var))) _ ->
252+
S.singleton var
253+
(_ :: HsExpr GhcTc) -> mempty
254+
) binds
255+
256+
238257
getSpanAndTypeAtHole
239258
:: Tracked age Range
240259
-> Tracked age (HieASTs b)
@@ -302,7 +321,7 @@ buildTopLevelHypothesis name ps = do
302321
-- | Construct a hypothesis for a single pattern, including building
303322
-- sub-hypotheses for constructor pattern matches.
304323
buildPatHy :: Provenance -> PatCompat GhcTc -> State Int (Hypothesis CType)
305-
buildPatHy prov (fromPatCompatTc -> p0) =
324+
buildPatHy prov (fromPatCompat -> p0) =
306325
case p0 of
307326
VarPat _ x -> pure $ mkIdHypothesis (unLoc x) prov
308327
LazyPat _ p -> buildPatHy prov p
@@ -317,7 +336,7 @@ buildPatHy prov (fromPatCompatTc -> p0) =
317336
ListPat x@(ListPatTc ty _) (p : ps) ->
318337
mkDerivedConHypothesis prov (RealDataCon consDataCon) [ty]
319338
[ (0, p)
320-
, (1, toPatCompatTc $ ListPat x ps)
339+
, (1, toPatCompat $ ListPat x ps)
321340
]
322341
-- Desugar tuples into an explicit constructor
323342
TuplePat tys pats boxity ->

plugins/hls-tactics-plugin/src/Wingman/LanguageServer/TacticProviders.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ filterBindingType
211211
-> TacticProvider
212212
filterBindingType p tp tpd =
213213
let jdg = tpd_jdg tpd
214-
hy = jHypothesis jdg
214+
hy = jLocalHypothesis jdg
215215
g = jGoal jdg
216216
in fmap join $ for (unHypothesis hy) $ \hi ->
217217
let ty = unCType $ hi_type hi

plugins/hls-tactics-plugin/src/Wingman/Simplify.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Development.IDE.GHC.Compat
1111
import GHC.SourceGen (var)
1212
import GHC.SourceGen.Expr (lambda)
1313
import Wingman.CodeGen.Utils
14-
import Wingman.GHC (containsHsVar, fromPatCompatPs)
14+
import Wingman.GHC (containsHsVar, fromPatCompat)
1515

1616

1717
------------------------------------------------------------------------------
@@ -20,7 +20,7 @@ pattern Lambda :: [Pat GhcPs] -> HsExpr GhcPs -> HsExpr GhcPs
2020
pattern Lambda pats body <-
2121
HsLam _
2222
(MG {mg_alts = L _ [L _
23-
(Match { m_pats = fmap fromPatCompatPs -> pats
23+
(Match { m_pats = fmap fromPatCompat -> pats
2424
, m_grhss = GRHSs {grhssGRHSs = [L _ (
2525
GRHS _ [] (L _ body))]}
2626
})]})

0 commit comments

Comments
 (0)