@@ -87,11 +87,21 @@ instance Semigroup RestrictItem where
8787 <> RestrictItem y1 y2 y3 y4 y5 y6 y7
8888 = RestrictItem (x1<> y1) (x2<> y2) (x3<> y3) (x4<> y4) (x5<> y5) (x6<> y6) (x7<> y7)
8989
90+ data RestrictFunctionItem = RestrictFunctionItem
91+ { rfiWithin :: [(String , String )]
92+ ,rfiMessage :: Maybe String
93+ ,rfiTypeApp :: Maybe RestrictTypeApp
94+ }
95+
96+ instance Semigroup RestrictFunctionItem where
97+ RestrictFunctionItem a1 a2 a3 <> RestrictFunctionItem b1 b2 b3 =
98+ RestrictFunctionItem (a1 <> b1) (a2 <> b2) (a3 <> b3)
99+
90100-- Contains a map from module (Nothing if the rule is unqualified) to (within, message), so that we can
91101-- distinguish functions with the same name.
92102-- For example, this allows us to have separate rules for "Data.Map.fromList" and "Data.Set.fromList".
93103-- Using newtype rather than type because we want to define (<>) as 'Map.unionWith (<>)'.
94- newtype RestrictFunction = RestrictFun (Map. Map (Maybe String ) ([( String , String )], Maybe String ) )
104+ newtype RestrictFunction = RestrictFun (Map. Map (Maybe String ) RestrictFunctionItem )
95105
96106instance Semigroup RestrictFunction where
97107 RestrictFun m1 <> RestrictFun m2 = RestrictFun (Map. unionWith (<>) m1 m2)
@@ -104,7 +114,11 @@ restrictions settings = (rFunction, rOthers)
104114 where
105115 (map snd -> rfs, ros) = partition ((== RestrictFunction ) . fst ) [(restrictType x, x) | SettingRestrict x <- settings]
106116 rFunction = (all restrictDefault rfs, Map. fromListWith (<>) [mkRf s r | r <- rfs, s <- restrictName r])
107- mkRf s Restrict {.. } = (name, RestrictFun $ Map. singleton modu (restrictWithin, restrictMessage))
117+ mkRf s Restrict {.. } = (name, RestrictFun $ Map. singleton modu RestrictFunctionItem
118+ { rfiWithin = restrictWithin
119+ , rfiMessage = restrictMessage
120+ , rfiTypeApp = restrictTypeApp
121+ })
108122 where
109123 -- Parse module and name from s. module = Nothing if the rule is unqualified.
110124 (modu, name) = first (fmap NonEmpty. init . NonEmpty. nonEmpty) (breakEnd (== ' .' ) s)
@@ -271,14 +285,57 @@ importListToIdents =
271285
272286checkFunctions :: Scope -> String -> [LHsDecl GhcPs ] -> RestrictFunctions -> [Idea ]
273287checkFunctions scope modu decls (def, mp) =
274- [ (ideaMessage message $ ideaNoTo $ warn " Avoid restricted function " (reLoc x) (reLoc x) [] ){ideaDecl = [dname]}
288+ [ (ideaMessage rfiMessage $ ideaNoTo $ warn hint (reLoc x) (reLoc x) [] ){ideaDecl = [dname]}
275289 | d <- decls
276290 , let dname = fromMaybe " " (declName d)
277291 , x <- universeBi d :: [LocatedN RdrName ]
278292 , let xMods = possModules scope x
279- , let (withins, message) = fromMaybe ([(" " ," " ) | def], Nothing ) (findFunction mp x xMods)
280- , not $ within modu dname withins
293+ , let RestrictFunctionItem {.. } = fromMaybe defaultRestrictFunction (findFunction mp x xMods)
294+ , let withinOk = within modu dname rfiWithin
295+ , let typeAppOk = maybe True (\ req -> typeAppSatisfies req typeAppHeads (locA $ getLoc x)) rfiTypeApp
296+ , let hint = case () of
297+ _ | not withinOk -> " Avoid restricted function"
298+ | otherwise -> typeAppHint rfiTypeApp
299+ , not withinOk || not typeAppOk
281300 ]
301+ where
302+ typeAppHeads = visibleTypeAppHeads decls
303+ defaultRestrictFunction = RestrictFunctionItem [(" " ," " ) | def] Nothing Nothing
304+
305+ typeAppHint :: Maybe RestrictTypeApp -> String
306+ typeAppHint (Just TypeAppRequired ) = " Use visible type application"
307+ typeAppHint (Just TypeAppForbidden ) = " Avoid visible type application"
308+ typeAppHint Nothing = " Avoid restricted function"
309+
310+ typeAppSatisfies :: RestrictTypeApp -> Set. Set SrcSpanD -> SrcSpan -> Bool
311+ typeAppSatisfies TypeAppRequired heads = (`Set.member` heads) . SrcSpanD
312+ typeAppSatisfies TypeAppForbidden heads = (`Set.notMember` heads) . SrcSpanD
313+
314+ visibleTypeAppHeads :: [LHsDecl GhcPs ] -> Set. Set SrcSpanD
315+ visibleTypeAppHeads decls =
316+ Set. fromList $ exprHeads ++ patHeads
317+ where
318+ exprHeads =
319+ [ SrcSpanD $ locA $ getLoc name
320+ | expr <- universeBi decls :: [LHsExpr GhcPs ]
321+ , L _ (HsAppType _ fun _) <- [expr]
322+ , Just name <- [typeAppHead fun]
323+ ]
324+ patHeads =
325+ [ SrcSpanD $ locA $ getLoc name
326+ | pat <- universeBi decls :: [LPat GhcPs ]
327+ , L _ (ConPat _ name details) <- [pat]
328+ , hasTypeApp details
329+ ]
330+ hasTypeApp (PrefixCon tyArgs _) = not $ null tyArgs
331+ hasTypeApp _ = False
332+
333+ typeAppHead :: LHsExpr GhcPs -> Maybe (LocatedN RdrName )
334+ typeAppHead (L _ (HsVar _ name)) = Just name
335+ typeAppHead (L _ (HsApp _ fun _)) = typeAppHead fun
336+ typeAppHead (L _ (HsAppType _ fun _)) = typeAppHead fun
337+ typeAppHead (L _ (HsPar _ fun)) = typeAppHead fun
338+ typeAppHead _ = Nothing
282339
283340-- Returns Just iff there are rules for x, which are either unqualified, or qualified with a module that is
284341-- one of x's possible modules.
@@ -288,7 +345,7 @@ findFunction
288345 :: Map. Map String RestrictFunction
289346 -> LocatedN RdrName
290347 -> [ModuleName ]
291- -> Maybe ([( String , String )], Maybe String )
348+ -> Maybe RestrictFunctionItem
292349findFunction restrictMap (rdrNameStr -> x) (map moduleNameString -> possMods) = do
293350 (RestrictFun mp) <- Map. lookup x restrictMap
294351 n <- NonEmpty. nonEmpty . Map. elems $ Map. filterWithKey (const . maybe True (`elem` possMods)) mp
0 commit comments