Florian Ragwitz pushed to branch wip/rafl/hpc-instances at Glasgow Haskell Compiler / GHC
Commits:
-
80aca549
by Florian Ragwitz at 2026-10-11T17:08:42-07:00
12 changed files:
- + changelog.d/hpc-classinsts
- compiler/GHC/Core/Opt/Specialise.hs
- compiler/GHC/CoreToIface.hs
- compiler/GHC/HsToCore.hs
- compiler/GHC/HsToCore/Ticks.hs
- compiler/GHC/Tc/TyCl/Instance.hs
- compiler/GHC/Types/Id/Info.hs
- + testsuite/tests/hpc/instmeths/instmeths.hs
- + testsuite/tests/hpc/instmeths/instmeths.stdout
- + testsuite/tests/hpc/instmeths/test.T
- testsuite/tests/simplCore/should_compile/T22375.stderr
- testsuite/tests/simplCore/should_compile/T22375DataFamily.stderr
Changes:
| 1 | +section: compiler
|
|
| 2 | +synopsis: Improve coverage reporting for class instances and their methods
|
|
| 3 | +description:
|
|
| 4 | + |
|
| 5 | + Coverage reporting (:ghc-flag:`-fhpc`) now treats class instances more
|
|
| 6 | + naturally.
|
|
| 7 | + |
|
| 8 | + Compiler-generated instance methods, such as inherited default methods, no
|
|
| 9 | + longer receive separate top-level coverage boxes. Explicitly implemented
|
|
| 10 | + instance methods retain their own declaration coverage.
|
|
| 11 | + |
|
| 12 | + This means that it is no longer necessary to exercise all of a class' methods
|
|
| 13 | + in order for it to appear "covered".
|
|
| 14 | + |
|
| 15 | + Additionally, top-level coverage boxes are now created for all instances of
|
|
| 16 | + classes with methods, and ticked by the use of *any* of the class' methods.
|
|
| 17 | + This indicates that an instance was in fact used at run-time.
|
|
| 18 | + |
|
| 19 | + Note that classes without any instance methods are currently excluded from
|
|
| 20 | + this, and won't have any coverage boxes created.
|
|
| 21 | + |
|
| 22 | +mrs: !16632
|
|
| 23 | +issues: #17155 #15932 |
| ... | ... | @@ -3082,6 +3082,7 @@ wantCallsFor _env f |
| 3082 | 3082 | JoinId {} -> True
|
| 3083 | 3083 | WorkerLikeId {} -> True
|
| 3084 | 3084 | RepPolyId {} -> True
|
| 3085 | + InstMethId {} -> True
|
|
| 3085 | 3086 | |
| 3086 | 3087 | interestingDict :: SpecEnv -> CoreExpr -> Bool
|
| 3087 | 3088 | -- This is a subtle and important function
|
| ... | ... | @@ -454,6 +454,7 @@ toIfaceTopBndr id |
| 454 | 454 | toIfaceIdDetails :: Name -- ^ for trace/panic messages only
|
| 455 | 455 | -> IdDetails -> IfaceIdDetails
|
| 456 | 456 | toIfaceIdDetails _ VanillaId = IfVanillaId
|
| 457 | +toIfaceIdDetails _ (InstMethId {}) = IfVanillaId
|
|
| 457 | 458 | toIfaceIdDetails _ (WorkerLikeId dmds) = IfWorkerLikeId dmds
|
| 458 | 459 | toIfaceIdDetails _ (DFunId {}) = IfDFunId
|
| 459 | 460 | toIfaceIdDetails _ (RecSelId { sel_naughty = n
|
| ... | ... | @@ -170,7 +170,8 @@ deSugar hsc_env |
| 170 | 170 | (hsc_logger hsc_env)
|
| 171 | 171 | (initTicksConfig (hsc_dflags hsc_env))
|
| 172 | 172 | mod mod_loc
|
| 173 | - export_set (typeEnvTyCons type_env) binds
|
|
| 173 | + export_set (typeEnvTyCons type_env)
|
|
| 174 | + insts binds
|
|
| 174 | 175 | else return (binds, Nothing)
|
| 175 | 176 | ; let modBreaks
|
| 176 | 177 | | Just (_, _, breakpointSpecs) <- m_tickInfo
|
| 1 | -{-# LANGUAGE NondecreasingIndentation, DataKinds #-}
|
|
| 1 | +{-# LANGUAGE NondecreasingIndentation, DataKinds, ViewPatterns #-}
|
|
| 2 | 2 | |
| 3 | 3 | {-# OPTIONS_GHC -Wno-incomplete-record-updates #-}
|
| 4 | 4 | |
| 5 | 5 | {-
|
| 6 | 6 | (c) Galois, 2006
|
| 7 | 7 | (c) University of Glasgow, 2007
|
| 8 | -(c) Florian Ragwitz, 2025
|
|
| 8 | +(c) Florian Ragwitz, 2025-2026
|
|
| 9 | 9 | -}
|
| 10 | 10 | |
| 11 | 11 | module GHC.HsToCore.Ticks
|
| ... | ... | @@ -24,6 +24,8 @@ import GHC.Unit |
| 24 | 24 | |
| 25 | 25 | import GHC.Core.Type
|
| 26 | 26 | import GHC.Core.TyCon
|
| 27 | +import GHC.Core.InstEnv
|
|
| 28 | +import GHC.Core.Class
|
|
| 27 | 29 | |
| 28 | 30 | import GHC.Data.Maybe
|
| 29 | 31 | import GHC.Data.FastString
|
| ... | ... | @@ -43,6 +45,7 @@ import GHC.Types.Var.Set |
| 43 | 45 | import GHC.Types.Var.Env
|
| 44 | 46 | import GHC.Types.Name.Set
|
| 45 | 47 | import GHC.Types.Name
|
| 48 | +import GHC.Types.Name.Env
|
|
| 46 | 49 | import GHC.Types.CostCentre
|
| 47 | 50 | import GHC.Types.CostCentre.State
|
| 48 | 51 | import GHC.Types.Tickish
|
| ... | ... | @@ -56,6 +59,7 @@ import Data.Foldable (toList) |
| 56 | 59 | import Trace.Hpc.Mix
|
| 57 | 60 | |
| 58 | 61 | import Data.Bifunctor (second)
|
| 62 | +import Data.Functor
|
|
| 59 | 63 | import Data.List.NonEmpty (NonEmpty (..))
|
| 60 | 64 | import Data.Set (Set)
|
| 61 | 65 | import qualified Data.Set as Set
|
| ... | ... | @@ -100,11 +104,12 @@ addTicksToBinds |
| 100 | 104 | -- isExportedId doesn't work yet (the desugarer
|
| 101 | 105 | -- hasn't set it), so we have to work from this set.
|
| 102 | 106 | -> [TyCon] -- ^ Type constructors in this module
|
| 107 | + -> [ClsInst] -- ^ Class instances in this module
|
|
| 103 | 108 | -> LHsBinds GhcTc
|
| 104 | 109 | -> IO (LHsBinds GhcTc, Maybe (FilePath, SizedSeq Tick, SizedSeq Tick))
|
| 105 | 110 | |
| 106 | 111 | addTicksToBinds logger cfg
|
| 107 | - mod mod_loc exports tyCons binds
|
|
| 112 | + mod mod_loc exports tyCons insts binds
|
|
| 108 | 113 | | let passes = ticks_passes cfg
|
| 109 | 114 | , not (null passes)
|
| 110 | 115 | , Just orig_file <- ml_hs_file mod_loc = do
|
| ... | ... | @@ -129,8 +134,13 @@ addTicksToBinds logger cfg |
| 129 | 134 | , this_mod = mod
|
| 130 | 135 | , tickishType = tickish
|
| 131 | 136 | , recSelBinds = emptyVarEnv
|
| 137 | + , instMeths = emptyVarEnv
|
|
| 138 | + , instTicks = emptyNameEnv
|
|
| 132 | 139 | }
|
| 133 | - (binds',_,st') = unTM (addTickLHsBinds binds) env st
|
|
| 140 | + addTick = do
|
|
| 141 | + instTicks <- allocInstTicks insts
|
|
| 142 | + withEnv (\e -> e{ instTicks }) $ addTickLHsBinds binds
|
|
| 143 | + (binds',_,st') = unTM addTick env st
|
|
| 134 | 144 | in (binds', st')
|
| 135 | 145 | |
| 136 | 146 | (binds1,st) = foldr tickPass (binds, initTTState) passes
|
| ... | ... | @@ -231,7 +241,7 @@ addTickLHsBind :: LHsBind GhcTc -> TM (LHsBind GhcTc) |
| 231 | 241 | addTickLHsBind (L pos (XHsBindsLR bind@(AbsBinds { abs_binds = binds
|
| 232 | 242 | , abs_exports = abs_exports
|
| 233 | 243 | }))) =
|
| 234 | - withEnv (add_rec_sels . add_inlines . add_exports) $ do
|
|
| 244 | + withEnv (add_inst_meths . add_rec_sels . add_inlines . add_exports) $ do
|
|
| 235 | 245 | binds' <- addTickLHsBinds binds
|
| 236 | 246 | return $ L pos $ XHsBindsLR $ bind { abs_binds = binds' }
|
| 237 | 247 | where
|
| ... | ... | @@ -259,19 +269,26 @@ addTickLHsBind (L pos (XHsBindsLR bind@(AbsBinds { abs_binds = binds |
| 259 | 269 | | ABE{ abe_poly, abe_mono } <- abs_exports
|
| 260 | 270 | , RecSelId{} <- [idDetails abe_poly] ] }
|
| 261 | 271 | |
| 272 | + add_inst_meths env =
|
|
| 273 | + env{ instMeths = instMeths env `extendVarEnvList`
|
|
| 274 | + [ (abe_mono, dfun)
|
|
| 275 | + | ABE{ abe_poly, abe_mono } <- abs_exports
|
|
| 276 | + , Just dfun <- [instMeth abe_poly] ] }
|
|
| 277 | + where instMeth (idDetails -> InstMethId dfun) = Just dfun
|
|
| 278 | + instMeth id = lookupVarEnv (instMeths env) id
|
|
| 279 | + |
|
| 262 | 280 | addTickLHsBind (L pos (funBind@(FunBind { fun_id = L _ id, fun_matches = matches }))) = do
|
| 263 | 281 | let name = getOccString id
|
| 264 | 282 | decl_path <- getPathEntry
|
| 265 | 283 | density <- getDensity
|
| 284 | + env <- getEnv
|
|
| 266 | 285 | |
| 267 | - inline_ids <- liftM inlines getEnv
|
|
| 268 | 286 | -- See Note [inline sccs]
|
| 269 | 287 | let inline = isInlinePragma (idInlinePragma id)
|
| 270 | - || id `elemVarSet` inline_ids
|
|
| 288 | + || id `elemVarSet` inlines env
|
|
| 271 | 289 | |
| 272 | 290 | -- See Note [inline sccs]
|
| 273 | - tickish <- tickishType `liftM` getEnv
|
|
| 274 | - case tickish of { ProfNotes | inline -> return (L pos funBind); _ -> do
|
|
| 291 | + case tickishType env of { ProfNotes | inline -> return (L pos funBind); _ -> do
|
|
| 275 | 292 | |
| 276 | 293 | -- See Note [Record-selector ticks]
|
| 277 | 294 | selTicks <- recSelTick id
|
| ... | ... | @@ -294,16 +311,28 @@ addTickLHsBind (L pos (funBind@(FunBind { fun_id = L _ id, fun_matches = matches |
| 294 | 311 | toplev = null decl_path
|
| 295 | 312 | exported = idName id `elemNameSet` exported_names
|
| 296 | 313 | |
| 297 | - tick <- if not blackListed &&
|
|
| 298 | - shouldTickBind density toplev exported simple inline
|
|
| 314 | + -- See Note [Instance Method Coverage].
|
|
| 315 | + -- We only exclude generated instance methods for now, not generated code in
|
|
| 316 | + -- general, whose coverage semantics may need to be considered separately.
|
|
| 317 | + let generatedInstMeth = tickishType env == HpcTicks
|
|
| 318 | + && id `elemVarEnv` instMeths env
|
|
| 319 | + && isGenerated (mg_origin (mg_ext matches))
|
|
| 320 | + |
|
| 321 | + tick <- if not generatedInstMeth
|
|
| 322 | + && not blackListed
|
|
| 323 | + && shouldTickBind density toplev exported simple inline
|
|
| 299 | 324 | then
|
| 300 | 325 | bindTick density name (locA pos) fvs
|
| 301 | 326 | else
|
| 302 | 327 | return Nothing
|
| 303 | 328 | |
| 329 | + -- See Note [Instance Method Coverage].
|
|
| 330 | + instTick <- instMethTick id
|
|
| 331 | + |
|
| 304 | 332 | let mbCons = maybe Prelude.id (:)
|
| 305 | 333 | return $ L pos $ funBind { fun_matches = mg
|
| 306 | - , fun_ext = second (tick `mbCons`) (fun_ext funBind) }
|
|
| 334 | + , fun_ext = second ((instTick `mbCons`) . (tick `mbCons`))
|
|
| 335 | + (fun_ext funBind) }
|
|
| 307 | 336 | } }
|
| 308 | 337 | where
|
| 309 | 338 | -- See Note [Record-selector ticks]
|
| ... | ... | @@ -338,6 +367,30 @@ addTickLHsBind (L pos (funBind@(FunBind { fun_id = L _ id, fun_matches = matches |
| 338 | 367 | -- This scheme could be extended further in the future, making coverage for
|
| 339 | 368 | -- positional constructor fields mean that the field was accessed at run-time.
|
| 340 | 369 | |
| 370 | +-- Note [Instance Method Coverage]
|
|
| 371 | +-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 372 | +-- Users expect source-level semantics for coverage reports (see #17155,
|
|
| 373 | +-- #15932). That is, code they've written themselves should be marked as covered
|
|
| 374 | +-- iff it is evaluated at run-time. Given that the implementation of class
|
|
| 375 | +-- instances creates top-level bindings for all methods of an instance,
|
|
| 376 | +-- inherited and hand-written, we need to go out of our way to create top-level
|
|
| 377 | +-- tick boxes only for methods which correspond to user-written code.
|
|
| 378 | +--
|
|
| 379 | +-- Instance method 'Id's carry the name of the 'DFunId' of the instance they
|
|
| 380 | +-- belong to. When creating tick boxes for top-level bindings, we can use this
|
|
| 381 | +-- information to skip creating boxes for inherited/generated instance methods.
|
|
| 382 | +--
|
|
| 383 | +-- Additionally, we also want to inform the user whether or not their class
|
|
| 384 | +-- instance as a whole has been used at run-time. To do so, we create a single
|
|
| 385 | +-- tick box at the instance head and tick that box when any method of that
|
|
| 386 | +-- instance is evaluated.
|
|
| 387 | +--
|
|
| 388 | +-- As marker/tag-like class instances without any methods would always appear
|
|
| 389 | +-- as uncovered using the above mechanism, we exclude them when creating
|
|
| 390 | +-- instance tick boxes. This could potentially be improved in the future by
|
|
| 391 | +-- tracking if any functions requiring an instance of an empty class have been
|
|
| 392 | +-- evaluated.
|
|
| 393 | + |
|
| 341 | 394 | -- TODO: Revisit this
|
| 342 | 395 | addTickLHsBind (L pos (pat@(PatBind { pat_lhs = lhs
|
| 343 | 396 | , pat_rhs = rhs
|
| ... | ... | @@ -1098,6 +1151,8 @@ data TickTransEnv = TTE { fileName :: FastString |
| 1098 | 1151 | , this_mod :: Module
|
| 1099 | 1152 | , tickishType :: TickishType
|
| 1100 | 1153 | , recSelBinds :: IdEnv DVarSet
|
| 1154 | + , instMeths :: IdEnv Name
|
|
| 1155 | + , instTicks :: NameEnv CoreTickish
|
|
| 1101 | 1156 | }
|
| 1102 | 1157 | |
| 1103 | 1158 | -- deriving Show
|
| ... | ... | @@ -1182,6 +1237,9 @@ getDensity = TM $ \env st -> (density env, noFVs, st) |
| 1182 | 1237 | ifDensity :: TickDensity -> TM a -> TM a -> TM a
|
| 1183 | 1238 | ifDensity d th el = do d0 <- getDensity; if d == d0 then th else el
|
| 1184 | 1239 | |
| 1240 | +ifTickish :: TickishType -> TM a -> TM a -> TM a
|
|
| 1241 | +ifTickish want th el = tickishType <$> getEnv >>= \got -> if want == got then th else el
|
|
| 1242 | + |
|
| 1185 | 1243 | getFreeVars :: TM a -> TM (FreeVars, a)
|
| 1186 | 1244 | getFreeVars (TM m)
|
| 1187 | 1245 | = TM $ \ env st -> case m env st of (a, fv, st') -> ((fv,a), fv, st')
|
| ... | ... | @@ -1238,6 +1296,20 @@ isBlackListed (RealSrcSpan pos _) = TM $ \ env st -> (Set.member pos (blackList |
| 1238 | 1296 | isBlackListed GeneratedSrcSpan{} = return False
|
| 1239 | 1297 | isBlackListed UnhelpfulSpan{} = return False
|
| 1240 | 1298 | |
| 1299 | +-- See Note [Instance Method Coverage].
|
|
| 1300 | +allocInstTicks :: [ClsInst] -> TM (NameEnv CoreTickish)
|
|
| 1301 | +allocInstTicks insts =
|
|
| 1302 | + ifTickish HpcTicks (mkNameEnv . catMaybes <$> mapM alloc insts) (pure emptyNameEnv)
|
|
| 1303 | + where
|
|
| 1304 | + alloc ClsInst{ is_dfun, is_cls }
|
|
| 1305 | + | null (classMethods is_cls) = pure Nothing
|
|
| 1306 | + | otherwise = fmap (idName is_dfun,) <$> allocATickBox (TopLevelBox [getOccString is_dfun])
|
|
| 1307 | + False True (getSrcSpan is_dfun) noFVs
|
|
| 1308 | + |
|
| 1309 | +-- See Note [Instance Method Coverage].
|
|
| 1310 | +instMethTick :: Id -> TM (Maybe CoreTickish)
|
|
| 1311 | +instMethTick id = getEnv <&> \e -> lookupVarEnv (instMeths e) id >>= lookupNameEnv (instTicks e)
|
|
| 1312 | + |
|
| 1241 | 1313 | -- the tick application inherits the source position of its
|
| 1242 | 1314 | -- expression argument to support nested box allocations
|
| 1243 | 1315 | allocTickBox :: BoxLabel -> Bool -> Bool -> SrcSpan -> TM (HsExpr GhcTc)
|
| ... | ... | @@ -1256,7 +1328,7 @@ allocTickBox boxLabel countEntries topOnly pos m |
| 1256 | 1328 | return (this_loc (XExpr $ HsTick tickish $ this_loc e))
|
| 1257 | 1329 | |
| 1258 | 1330 | recSelTick :: Id -> TM (Maybe [CoreTickish])
|
| 1259 | -recSelTick id = ifDensity TickForCoverage maybe_tick (pure Nothing)
|
|
| 1331 | +recSelTick id = ifTickish HpcTicks maybe_tick (pure Nothing)
|
|
| 1260 | 1332 | where
|
| 1261 | 1333 | maybe_tick = getEnv >>=
|
| 1262 | 1334 | maybe (pure Nothing) tick_all . (`lookupVarEnv` id) . recSelBinds
|
| ... | ... | @@ -59,7 +59,7 @@ import GHC.Core.FamInstEnv |
| 59 | 59 | import GHC.Core.Type
|
| 60 | 60 | import GHC.Core.Multiplicity
|
| 61 | 61 | import GHC.Core.InstEnv
|
| 62 | -import GHC.Core.Predicate( classMethodInstTy )
|
|
| 62 | +import GHC.Core.Predicate( classMethodInstTy, isCoVarType )
|
|
| 63 | 63 | import GHC.Core.TyCon
|
| 64 | 64 | import GHC.Core.Coercion.Axiom
|
| 65 | 65 | import GHC.Core.DataCon
|
| ... | ... | @@ -72,6 +72,7 @@ import GHC.Types.Var.Set |
| 72 | 72 | import GHC.Types.Basic
|
| 73 | 73 | import GHC.Types.ForeignCall ( typeCheckCType )
|
| 74 | 74 | import GHC.Types.Id
|
| 75 | +import GHC.Types.Id.Info
|
|
| 75 | 76 | import GHC.Types.InlinePragma
|
| 76 | 77 | import GHC.Types.SourceFile
|
| 77 | 78 | import GHC.Types.SourceText
|
| ... | ... | @@ -1859,7 +1860,7 @@ tcMethods _skol_info dfun_id clas tyvars dfun_ev_vars inst_tys |
| 1859 | 1860 | tc_item :: ClassOpItem -> TcM (Id, LHsBind GhcTc, Maybe Implication)
|
| 1860 | 1861 | tc_item (sel_id, dm_info)
|
| 1861 | 1862 | | Just (user_bind, bndr_loc, prags) <- findMethodBind (idName sel_id) binds prag_fn
|
| 1862 | - = tcMethodBody False clas tyvars dfun_ev_vars inst_tys
|
|
| 1863 | + = tcMethodBody False dfun_id clas tyvars dfun_ev_vars inst_tys
|
|
| 1863 | 1864 | dfun_ev_binds is_derived hs_sig_fn
|
| 1864 | 1865 | spec_inst_prags prags
|
| 1865 | 1866 | sel_id user_bind bndr_loc
|
| ... | ... | @@ -1879,7 +1880,7 @@ tcMethods _skol_info dfun_id clas tyvars dfun_ev_vars inst_tys |
| 1879 | 1880 | -- See Note [Implementation of Unsatisfiable constraints],
|
| 1880 | 1881 | -- in GHC.Tc.Errors, point (D).
|
| 1881 | 1882 | _ | (theta_id,unsat_msg) : _ <- unsat_thetas
|
| 1882 | - -> do { (meth_id, _) <- mkMethIds clas tyvars dfun_ev_vars
|
|
| 1883 | + -> do { (meth_id, _) <- mkMethIds dfun_id clas tyvars dfun_ev_vars
|
|
| 1883 | 1884 | inst_tys sel_id
|
| 1884 | 1885 | ; unsat_id <- tcLookupKnownKeyId unsatisfiableIdKey
|
| 1885 | 1886 | -- Recall that unsatisfiable :: forall {rep} (msg :: ErrorMessage) (a :: TYPE rep). Unsatisfiable msg => a
|
| ... | ... | @@ -1896,7 +1897,7 @@ tcMethods _skol_info dfun_id clas tyvars dfun_ev_vars inst_tys |
| 1896 | 1897 | Just (dm_name, dm_spec) ->
|
| 1897 | 1898 | do { (meth_bind, inline_prags) <- mkDefMethBind inst_loc dfun_id clas sel_id dm_name dm_spec
|
| 1898 | 1899 | ; tcMethodBody (is_vanilla_dm dm_spec)
|
| 1899 | - clas tyvars dfun_ev_vars inst_tys
|
|
| 1900 | + dfun_id clas tyvars dfun_ev_vars inst_tys
|
|
| 1900 | 1901 | dfun_ev_binds is_derived hs_sig_fn
|
| 1901 | 1902 | spec_inst_prags inline_prags
|
| 1902 | 1903 | sel_id meth_bind inst_loc }
|
| ... | ... | @@ -1904,7 +1905,7 @@ tcMethods _skol_info dfun_id clas tyvars dfun_ev_vars inst_tys |
| 1904 | 1905 | -- No default method
|
| 1905 | 1906 | Nothing ->
|
| 1906 | 1907 | do { traceTc "tc_def: warn" (ppr sel_id)
|
| 1907 | - ; (meth_id, _) <- mkMethIds clas tyvars dfun_ev_vars
|
|
| 1908 | + ; (meth_id, _) <- mkMethIds dfun_id clas tyvars dfun_ev_vars
|
|
| 1908 | 1909 | inst_tys sel_id
|
| 1909 | 1910 | ; dflags <- getDynFlags
|
| 1910 | 1911 | -- Add a binding whose RHS is an error
|
| ... | ... | @@ -2030,13 +2031,13 @@ Instead, we take the following approach: |
| 2030 | 2031 | tcMethodBody :: Bool -- True <=> This is a vanilla default method
|
| 2031 | 2032 | -- See (TRC5) in Note [Tracking needed EvIds]
|
| 2032 | 2033 | -- in GHC.Tc.Solver.Solve
|
| 2033 | - -> Class -> [TcTyVar] -> [EvVar] -> [TcType]
|
|
| 2034 | + -> DFunId -> Class -> [TcTyVar] -> [EvVar] -> [TcType]
|
|
| 2034 | 2035 | -> TcEvBinds -> Bool
|
| 2035 | 2036 | -> HsSigFun
|
| 2036 | 2037 | -> [LTcSpecPrag] -> [LSig GhcRn]
|
| 2037 | 2038 | -> Id -> LHsBind GhcRn -> SrcSpan
|
| 2038 | 2039 | -> TcM (TcId, LHsBind GhcTc, Maybe Implication)
|
| 2039 | -tcMethodBody is_vanilla_dm clas tyvars dfun_ev_vars inst_tys
|
|
| 2040 | +tcMethodBody is_vanilla_dm dfun_id clas tyvars dfun_ev_vars inst_tys
|
|
| 2040 | 2041 | dfun_ev_binds is_derived
|
| 2041 | 2042 | sig_fn spec_inst_prags prags
|
| 2042 | 2043 | sel_id (L bind_loc meth_bind) bndr_loc
|
| ... | ... | @@ -2044,7 +2045,7 @@ tcMethodBody is_vanilla_dm clas tyvars dfun_ev_vars inst_tys |
| 2044 | 2045 | do { traceTc "tcMethodBody" (ppr sel_id <+> ppr (idType sel_id) $$ ppr bndr_loc)
|
| 2045 | 2046 | ; let skol_info = MethSkol meth_name is_vanilla_dm
|
| 2046 | 2047 | ; (global_meth_id, local_meth_id) <- setSrcSpan bndr_loc $
|
| 2047 | - mkMethIds clas tyvars dfun_ev_vars
|
|
| 2048 | + mkMethIds dfun_id clas tyvars dfun_ev_vars
|
|
| 2048 | 2049 | inst_tys sel_id
|
| 2049 | 2050 | |
| 2050 | 2051 | ; let lm_bind = meth_bind { fun_id = L (noAnnSrcSpan bndr_loc)
|
| ... | ... | @@ -2162,16 +2163,19 @@ tcMethodBodyHelp hs_sig_fn sel_id local_meth_id meth_bind |
| 2162 | 2163 | -- they are all for meth_id
|
| 2163 | 2164 | |
| 2164 | 2165 | ------------------------
|
| 2165 | -mkMethIds :: Class -> [TcTyVar] -> [EvVar]
|
|
| 2166 | +mkMethIds :: DFunId -> Class -> [TcTyVar] -> [EvVar]
|
|
| 2166 | 2167 | -> [TcType] -> Id -> TcM (TcId, TcId)
|
| 2167 | 2168 | -- returns (poly_id, local_id), but ignoring any instance signature
|
| 2168 | 2169 | -- See Note [Instance method signatures]
|
| 2169 | -mkMethIds clas tyvars dfun_ev_vars inst_tys sel_id
|
|
| 2170 | +mkMethIds dfun_id clas tyvars dfun_ev_vars inst_tys sel_id
|
|
| 2170 | 2171 | = do { poly_meth_name <- newName (mkClassOpAuxOcc sel_occ)
|
| 2171 | 2172 | ; local_meth_name <- newName sel_occ
|
| 2172 | 2173 | -- Base the local_meth_name on the selector name, because
|
| 2173 | 2174 | -- type errors from tcMethodBody come from here
|
| 2174 | - ; let poly_meth_id = mkLocalId poly_meth_name ManyTy poly_meth_ty
|
|
| 2175 | + ; let poly_meth_id =
|
|
| 2176 | + assert (not (isCoVarType poly_meth_ty)) $
|
|
| 2177 | + Var.mkLocalVar (InstMethId (idName dfun_id))
|
|
| 2178 | + poly_meth_name ManyTy poly_meth_ty vanillaIdInfo
|
|
| 2175 | 2179 | local_meth_id = mkLocalId local_meth_name ManyTy local_meth_ty
|
| 2176 | 2180 | |
| 2177 | 2181 | ; return (poly_meth_id, local_meth_id) }
|
| ... | ... | @@ -222,6 +222,10 @@ data IdDetails |
| 222 | 222 | -- - the [CbvMark] is always empty (and ignored) until after Tidy
|
| 223 | 223 | -- for ids from the current module
|
| 224 | 224 | -- - If non-empty, at least is isMarkedCbbv; see (CBV2)
|
| 225 | + | InstMethId
|
|
| 226 | + -- ^ An instance method.
|
|
| 227 | + Name
|
|
| 228 | + -- ^ 'Name' of the dictionary function the instance method belongs to.
|
|
| 225 | 229 | |
| 226 | 230 | data RecSelInfo
|
| 227 | 231 | = RSI { rsi_def :: [ConLike] -- Record selector defined for these
|
| ... | ... | @@ -449,6 +453,7 @@ pprIdDetails other = brackets (pp other) |
| 449 | 453 | ppWhen is_naughty (text "(naughty)")
|
| 450 | 454 | pp CoVarId = text "CoVarId"
|
| 451 | 455 | pp (JoinId arity marks) = text "JoinId" <> parens (int arity) <> parens (ppr marks)
|
| 456 | + pp (InstMethId name) = text "InstMethId" <> parens (ppr name)
|
|
| 452 | 457 | |
| 453 | 458 | {-
|
| 454 | 459 | ************************************************************************
|
| 1 | +import Data.List
|
|
| 2 | +import Data.Maybe
|
|
| 3 | +import Trace.Hpc.Mix
|
|
| 4 | +import Trace.Hpc.Reflect
|
|
| 5 | +import Trace.Hpc.Tix
|
|
| 6 | +import Trace.Hpc.Util
|
|
| 7 | + |
|
| 8 | +class Foo a where
|
|
| 9 | + defMeth, defMeth', reqMeth :: a -> String
|
|
| 10 | + defMeth = const "class default"
|
|
| 11 | + defMeth' = const "class default"
|
|
| 12 | + |
|
| 13 | +newtype T a = T a deriving (Show, Eq, Functor)
|
|
| 14 | +instance Foo (T a) where
|
|
| 15 | + reqMeth = const "Foo (T a) instance"
|
|
| 16 | + |
|
| 17 | +newtype T' a = T' a
|
|
| 18 | + |
|
| 19 | +instance Foo (T' a) where
|
|
| 20 | + reqMeth = const "Foo (T' a) instance"
|
|
| 21 | + defMeth = const "Foo (T' a) instance"
|
|
| 22 | + |
|
| 23 | +-- no method use could "cover" this instance, so we (for now) omit the top-level box for it
|
|
| 24 | +class Marker a
|
|
| 25 | +instance Marker (T Bool)
|
|
| 26 | + |
|
| 27 | +interesting :: (Int, MixEntry) -> Maybe (Int, HpcPos, String)
|
|
| 28 | +interesting (n, (pos, TopLevelBox [name])) | isInteresting name = Just (n, pos, name)
|
|
| 29 | + where isInteresting = (||) <$> isInst <*> isMeth
|
|
| 30 | + isInst = ("$f" `isPrefixOf`)
|
|
| 31 | + isMeth = (||) <$> (`elem` ["(==)", "(/=)", "show", "showList", "showsPrec", "fmap", "(<$)"])
|
|
| 32 | + <*> ("Meth" `isInfixOf`)
|
|
| 33 | +interesting _ = Nothing
|
|
| 34 | + |
|
| 35 | +-- candidate for HPC.Utils, maybe?
|
|
| 36 | +sourceAt :: String -> HpcPos -> String
|
|
| 37 | +sourceAt src pos
|
|
| 38 | + | l1 == l2 = take (c2 - c1 + 1) . drop (c1 - 1) $ ls !! (l1 - 1)
|
|
| 39 | + | otherwise = intercalate "\n" $ first : (middle ++ [last])
|
|
| 40 | + where
|
|
| 41 | + (l1, c1, l2, c2) = fromHpcPos pos
|
|
| 42 | + ls = lines src
|
|
| 43 | + first = drop (c1 - 1) $ ls !! (l1 - 1)
|
|
| 44 | + middle = take (l2 - l1 - 1) $ drop l1 ls
|
|
| 45 | + last = take c2 $ ls !! (l2 - 1)
|
|
| 46 | + |
|
| 47 | +main :: IO ()
|
|
| 48 | +main = do
|
|
| 49 | + print (T 23 == (succ <$> T 22)) -- tick Eq and Functor by using any of their methods
|
|
| 50 | + print (reqMeth (T' 1)) -- tick `Foo (T' a)` and that instance's `reqMeth`
|
|
| 51 | + |
|
| 52 | + Mix source _ _ _ mixEntries <- readMix [".hpc"] (Left "Main")
|
|
| 53 | + src <- readFileUtf8 source
|
|
| 54 | + let boxes = mapMaybe interesting $ zip [0 ..] mixEntries
|
|
| 55 | + Tix [TixModule "Main" _ _ counts] <- examineTix
|
|
| 56 | + mapM_ print [ (counts !! n, path, sourceAt src pos)
|
|
| 57 | + | (n, pos, path) <- sortOn (\(_, l, p) -> (p, l)) boxes ] |
| 1 | +True
|
|
| 2 | +"Foo (T' a) instance"
|
|
| 3 | +(1,"$fEqT","Eq")
|
|
| 4 | +(0,"$fFooT","Foo (T a)")
|
|
| 5 | +(1,"$fFooT'","Foo (T' a)")
|
|
| 6 | +(1,"$fFunctorT","Functor")
|
|
| 7 | +(0,"$fShowT","Show")
|
|
| 8 | +(0,"defMeth","defMeth = const \"class default\"")
|
|
| 9 | +(0,"defMeth","defMeth = const \"Foo (T' a) instance\"")
|
|
| 10 | +(0,"defMeth'","defMeth' = const \"class default\"")
|
|
| 11 | +(0,"reqMeth","reqMeth = const \"Foo (T a) instance\"")
|
|
| 12 | +(1,"reqMeth","reqMeth = const \"Foo (T' a) instance\"") |
| 1 | +setTestOpts([omit_ghci, when(fast(), skip), js_skip])
|
|
| 2 | + |
|
| 3 | +test('instmeths',
|
|
| 4 | + [ignore_extension],
|
|
| 5 | + compile_and_run, ['-fhpc']) |
| ... | ... | @@ -5,7 +5,7 @@ Result size of Tidy Core |
| 5 | 5 | |
| 6 | 6 | -- RHS size: {terms: 14, types: 9, coercions: 0, joins: 0/0}
|
| 7 | 7 | T22375.$fEqX_$c== :: X -> X -> Bool
|
| 8 | -[GblId,
|
|
| 8 | +[GblId[InstMethId(T22375.$fEqX)],
|
|
| 9 | 9 | Arity=2,
|
| 10 | 10 | Str=<SL><SL>,
|
| 11 | 11 | Unf=Unf{Src=<vanilla>, TopLvl=True,
|
| ... | ... | @@ -27,7 +27,7 @@ T22375.$fEqX_$c== |
| 27 | 27 | |
| 28 | 28 | -- RHS size: {terms: 18, types: 9, coercions: 0, joins: 0/0}
|
| 29 | 29 | T22375.$fEqX_$c/= [InlPrag=INLINE (sat-args=2)] :: X -> X -> Bool
|
| 30 | -[GblId,
|
|
| 30 | +[GblId[InstMethId(T22375.$fEqX)],
|
|
| 31 | 31 | Arity=2,
|
| 32 | 32 | Str=<SL><SL>,
|
| 33 | 33 | Unf=Unf{Src=StableUser, TopLvl=True,
|
| ... | ... | @@ -5,7 +5,7 @@ Result size of Tidy Core |
| 5 | 5 | |
| 6 | 6 | -- RHS size: {terms: 14, types: 11, coercions: 2, joins: 0/0}
|
| 7 | 7 | T22375DataFamily.$fEqX_$c== :: X () -> X () -> Bool
|
| 8 | -[GblId,
|
|
| 8 | +[GblId[InstMethId(T22375DataFamily.$fEqX)],
|
|
| 9 | 9 | Arity=2,
|
| 10 | 10 | Str=<SL><SL>,
|
| 11 | 11 | Unf=Unf{Src=<vanilla>, TopLvl=True,
|
| ... | ... | @@ -36,7 +36,7 @@ T22375DataFamily.$fEqX_$c== |
| 36 | 36 | -- RHS size: {terms: 18, types: 11, coercions: 2, joins: 0/0}
|
| 37 | 37 | T22375DataFamily.$fEqX_$c/= [InlPrag=INLINE (sat-args=2)]
|
| 38 | 38 | :: X () -> X () -> Bool
|
| 39 | -[GblId,
|
|
| 39 | +[GblId[InstMethId(T22375DataFamily.$fEqX)],
|
|
| 40 | 40 | Arity=2,
|
| 41 | 41 | Str=<SL><SL>,
|
| 42 | 42 | Unf=Unf{Src=StableUser, TopLvl=True,
|