Skip to content

Commit a4d0018

Browse files
committed
Fix treatment of coercion variables
1 parent b5c017c commit a4d0018

File tree

4 files changed

+113
-37
lines changed

4 files changed

+113
-37
lines changed

changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11

2+
# Version 0.6.0.0 (2025-03-29)
3+
4+
- Fixed the treatment of constraints containing coercion variables; the plugin
5+
will no longer crash upon encountering such constraints.
6+
27
# Version 0.5.2.0 (2024-11-29)
38

49
- Update to support GHC 9.10 and 9.12.

if-instance.cabal

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cabal-version: 3.0
22
name: if-instance
3-
version: 0.5.2.0
3+
version: 0.6.0.0
44
synopsis: Branch on whether a constraint is satisfied
55
license: BSD-3-Clause
66
build-type: Simple
@@ -86,6 +86,8 @@ library
8686
build-depends:
8787
ghc-tcplugin-api
8888
>= 0.13 && < 0.15,
89+
transformers
90+
>= 0.5.6.2 && < 0.7
8991

9092
exposed-modules:
9193
Data.Constraint.If

src/IfSat/Plugin.hs

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
{-# LANGUAGE BlockArguments #-}
21
{-# LANGUAGE CPP #-}
2+
3+
{-# LANGUAGE BlockArguments #-}
34
{-# LANGUAGE DataKinds #-}
45
{-# LANGUAGE NamedFieldPuns #-}
56
{-# LANGUAGE PatternSynonyms #-}
@@ -10,10 +11,8 @@ module IfSat.Plugin
1011
where
1112

1213
-- base
13-
import Control.Monad
14-
( filterM )
1514
import Data.Foldable
16-
( for_ )
15+
( traverse_ )
1716
import Data.Maybe
1817
( catMaybes, mapMaybe )
1918

@@ -35,12 +34,8 @@ import GHC.Tc.Types
3534
( TcM )
3635
import GHC.Tc.Types.Constraint
3736
( isEmptyWC, CtEvidence (..), ctEvEvId )
38-
import GHC.Tc.Utils.TcType
39-
( MetaDetails(..), metaTyVarRef
40-
, tyCoVarsOfTypeList
41-
)
4237
import GHC.Tc.Utils.TcMType
43-
( isUnfilledMetaTyVar, newTcEvBinds )
38+
( newTcEvBinds )
4439

4540
-- ghc-tcplugin-api
4641
import GHC.TcPlugin.API
@@ -49,7 +44,9 @@ import GHC.TcPlugin.API.Internal
4944

5045
-- if-instance
5146
import IfSat.Plugin.Compat
52-
( wrapTcS, getRestoreTcS )
47+
( wrapTcS, getRestoreTcS
48+
, unfilledRefsOfType, unfillMutableRef
49+
)
5350

5451
--------------------------------------------------------------------------------
5552
-- Plugin definition.
@@ -151,9 +148,7 @@ solveWanted defs@( PluginDefs { orClass } ) givens wanted
151148

152149
-- Keep track of the current solver state in order to backtrack
153150
-- in the event that our attempt at solving 'ct_l' fails.
154-
ct_l_unfilled_metas <- wrapTcS
155-
$ filterM isUnfilledMetaTyVar
156-
$ tyCoVarsOfTypeList ct_l_ty
151+
ct_l_unfilled <- wrapTcS $ unfilledRefsOfType ct_l_ty
157152
restoreTcS <- getRestoreTcS
158153

159154
-- Try to solve 'ct_l', using both Givens and top-level instances.
@@ -183,11 +178,8 @@ solveWanted defs@( PluginDefs { orClass } ) givens wanted
183178
-- Reset the solver state to before we attempted to solve 'ct_l',
184179
-- and undo any type variable unifications that happened.
185180
restoreTcS
186-
wrapTcS $ for_ ct_l_unfilled_metas \ meta ->
187-
writeTcRef ( metaTyVarRef meta ) Flexi
188-
ct_r_unfilled_metas <- wrapTcS
189-
$ filterM isUnfilledMetaTyVar
190-
$ tyCoVarsOfTypeList ct_r_ty
181+
wrapTcS $ traverse_ unfillMutableRef ct_l_unfilled
182+
ct_r_unfilled <- wrapTcS $ unfilledRefsOfType ct_r_ty
191183

192184
-- Try to solve 'ct_r', using both Givens and top-level instances.
193185
residual_ct_r <- solveSimpleWanteds ( unitBag ct_r )
@@ -212,8 +204,7 @@ solveWanted defs@( PluginDefs { orClass } ) givens wanted
212204
-- Reset the solver state to before we attempted to solve 'ct_r',
213205
-- and undo any type variable unifications that happened.
214206
restoreTcS
215-
wrapTcS $ for_ ct_r_unfilled_metas \ meta ->
216-
writeTcRef ( metaTyVarRef meta ) Flexi
207+
wrapTcS $ traverse_ unfillMutableRef ct_r_unfilled
217208

218209
pure Nothing
219210
pure $ ( , wanted ) <$> mb_wanted_evTerm
@@ -356,8 +347,8 @@ usedGivenCoercions givens ev = mapMaybe dep_cv givens
356347
-- selector warnings.
357348
wantedEvDest :: HasDebugCallStack => CtEvidence -> TcEvDest
358349
wantedEvDest ( CtWanted { ctev_dest = dst } ) = dst
359-
wantedEvDest g@( CtGiven {} ) =
360-
pprPanic "wantedEvDest called on CtGiven" (ppr g)
350+
wantedEvDest non_wtd =
351+
pprPanic "wantedEvDest, but not a Wanted Ct" (ppr non_wtd)
361352

362353
--------------------------------------------------------------------------------
363354

@@ -383,9 +374,7 @@ isSatRewriter ( PluginDefs { isSatTyCon } ) givens [ct_ty] = do
383374

384375
-- Keep track of the current solver state in order to undo any
385376
-- side-effects after calling 'solveSimpleWanteds' on 'ct'.
386-
ct_unfilled_metas <- wrapTcS
387-
$ filterM isUnfilledMetaTyVar
388-
$ tyCoVarsOfTypeList ct_ty
377+
ct_unfilled <- wrapTcS $ unfilledRefsOfType ct_ty
389378
restoreTcS <- getRestoreTcS
390379

391380
-- Try to solve 'ct', using both Givens and top-level instances.
@@ -396,8 +385,7 @@ isSatRewriter ( PluginDefs { isSatTyCon } ) givens [ct_ty] = do
396385
-- Reset the solver state to before we attempted to solve 'ct',
397386
-- and undo any type variable unifications that happened.
398387
restoreTcS
399-
wrapTcS $ for_ ct_unfilled_metas \ meta ->
400-
writeTcRef ( metaTyVarRef meta ) Flexi
388+
wrapTcS $ traverse_ unfillMutableRef ct_unfilled
401389

402390
let
403391
is_sat :: Bool

src/IfSat/Plugin/Compat.hs

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,61 @@
1-
{-# LANGUAGE BlockArguments #-}
21
{-# LANGUAGE CPP #-}
32

3+
{-# LANGUAGE BlockArguments #-}
4+
{-# LANGUAGE LambdaCase #-}
5+
{-# LANGUAGE TypeApplications #-}
6+
47
{-# OPTIONS_GHC -Wno-unused-top-binds #-}
58

69
module IfSat.Plugin.Compat
7-
( wrapTcS, getRestoreTcS )
10+
( -- * Dealing with TcS state
11+
wrapTcS, getRestoreTcS
12+
-- * Dealing with mutable references
13+
, UnfilledRef(..), unfilledRefsOfType, unfillMutableRef
14+
)
815
where
916

1017
-- base
18+
import Control.Monad
19+
( when )
1120
import Unsafe.Coerce
1221
( unsafeCoerce )
1322

1423
-- ghc
24+
import GHC.Core.Coercion
25+
( mkCoVarCo, mkHoleCo, coVarName, coHoleCoVar )
26+
import GHC.Core.Type
27+
( TyCoMapper(..), mapTyCo )
1528
#if MIN_VERSION_ghc(9,4,0)
1629
import GHC.Tc.Solver.InertSet
17-
( WorkList, InertSet )
30+
( WorkList )
1831
#endif
19-
import GHC.Tc.Solver.Monad
20-
( TcS
2132
#if MIN_VERSION_ghc(9,1,0)
22-
, TcLevel, wrapTcS
33+
import GHC.Tc.Solver.Monad
34+
( wrapTcS )
2335
#endif
2436
#if !MIN_VERSION_ghc(9,4,0)
25-
, WorkList, InertSet
37+
import GHC.Tc.Solver.Monad
38+
( WorkList )
2639
#endif
27-
)
2840
import GHC.Tc.Types
2941
( TcM, TcRef )
42+
43+
import GHC.Tc.Utils.TcMType
44+
( isUnfilledMetaTyVar )
45+
import GHC.Tc.Utils.TcType
46+
( MetaDetails(..), metaTyVarRef )
3047
import GHC.Tc.Types.Evidence
3148
( EvBindsVar(..) )
3249

50+
-- transformers
51+
import Control.Monad.Trans.Class ( lift )
52+
import Control.Monad.Trans.Writer.CPS ( WriterT )
53+
import qualified Control.Monad.Trans.Writer.CPS as Writer
54+
3355
-- ghc-tcplugin-api
3456
import GHC.TcPlugin.API
35-
( readTcRef, writeTcRef )
57+
import GHC.Types.Name.Env
58+
import GHC.Types.Var (tyVarName)
3659

3760
--------------------------------------------------------------------------------
3861

@@ -107,3 +130,61 @@ data ShimTcSEnv
107130
#endif
108131
, shim_tcs_worklist :: TcRef WorkList
109132
}
133+
134+
--------------------------------------------------------------------------------
135+
136+
-- | A mutable reference that was originally unfilled
137+
data UnfilledRef
138+
-- | A metavariable that was originally unfilled
139+
= UnfilledMeta !( TcRef MetaDetails )
140+
-- | A coercion hole that was originally unfilled
141+
| UnfilledCoHole !( TcRef ( Maybe Coercion ) )
142+
143+
-- | Gather all the unfilled mutable references of a type: unfilled
144+
-- metavariables and unfilled coercion holes.
145+
unfilledRefsOfType :: TcType -> TcM [ UnfilledRef ]
146+
unfilledRefsOfType ty0
147+
= fmap
148+
#if MIN_VERSION_ghc(9,3,0)
149+
nonDetNameEnvElts
150+
#else
151+
nameEnvElts
152+
#endif
153+
$ Writer.execWriterT
154+
$ go_ty ty0
155+
where
156+
(go_ty, _go_tys, _go_co, _go_cos) =
157+
mapTyCo @( WriterT ( NameEnv UnfilledRef ) TcM ) $
158+
TyCoMapper
159+
{ tcm_tyvar = \ _ tv -> do
160+
unfilled_meta <- lift $ isUnfilledMetaTyVar tv
161+
when unfilled_meta do
162+
let nm = tyVarName tv
163+
ref = UnfilledMeta $ metaTyVarRef tv
164+
Writer.tell $ unitNameEnv nm ref
165+
return $ mkTyVarTy tv
166+
, tcm_tycobinder =
167+
#if MIN_VERSION_ghc(9,7,0)
168+
\ () tv _ftf k -> k () tv
169+
#else
170+
\ () tv _af -> return ( (), tv )
171+
#endif
172+
, tcm_tycon = return
173+
, tcm_covar = \ _ cv -> return $ mkCoVarCo cv
174+
, tcm_hole = \ _ hole@(CoercionHole { ch_ref = hole_ref }) -> do
175+
hole_contents <- lift $ readTcRef hole_ref
176+
case hole_contents of
177+
Nothing -> do
178+
let nm = coVarName $ coHoleCoVar hole
179+
ref = UnfilledCoHole hole_ref
180+
Writer.tell $ unitNameEnv nm ref
181+
Just {} ->
182+
return ()
183+
return $ mkHoleCo hole
184+
}
185+
186+
-- | Restore a mutable reference to the unfilled state.
187+
unfillMutableRef :: UnfilledRef -> TcM ()
188+
unfillMutableRef = \case
189+
UnfilledMeta ref -> writeTcRef ref Flexi
190+
UnfilledCoHole hole -> writeTcRef hole Nothing

0 commit comments

Comments
 (0)