mirror of
https://github.com/gren-lang/compiler.git
synced 2024-09-11 08:45:42 +03:00
Make sure that Debug module is not used in --optimize mode
This is neat because you get one error message telling you all the modules that have Debug remnants. It is also neat because the Generate.Nitpick module seems like the perfect place for things like “efficiently detect globally unused definitions” and “detect packages that are no longer used”
This commit is contained in:
parent
dac392a778
commit
6be8d82c67
@ -86,4 +86,5 @@ generateDocs summary@(Summary.Summary root project _ _ _) =
|
||||
(dirty, ifaces) <- Plan.plan (Just docsPath) summary graph
|
||||
answers <- Compile.compile project (Just docsPath) ifaces dirty
|
||||
results <- Artifacts.ignore answers
|
||||
Output.noDebugUsesInPackage summary graph
|
||||
Artifacts.writeDocs results docsPath
|
||||
|
99
builder/src/Generate/Nitpick.hs
Normal file
99
builder/src/Generate/Nitpick.hs
Normal file
@ -0,0 +1,99 @@
|
||||
module Generate.Nitpick
|
||||
( findDebugUses
|
||||
)
|
||||
where
|
||||
|
||||
|
||||
import qualified Data.Map as Map
|
||||
import qualified Data.Set as Set
|
||||
|
||||
import qualified AST.Optimized as Opt
|
||||
import qualified AST.Module.Name as ModuleName
|
||||
import qualified Elm.Package as Pkg
|
||||
import qualified Elm.Name as N
|
||||
|
||||
|
||||
|
||||
-- FIND DEBUG USES
|
||||
|
||||
|
||||
findDebugUses :: Pkg.Name -> Opt.Graph -> [N.Name]
|
||||
findDebugUses pkg (Opt.Graph _ graph _) =
|
||||
Set.toList $ Map.foldrWithKey (addDebugUses pkg) Set.empty graph
|
||||
|
||||
|
||||
addDebugUses :: Pkg.Name -> Opt.Global -> Opt.Node -> Set.Set N.Name -> Set.Set N.Name
|
||||
addDebugUses here (Opt.Global (ModuleName.Canonical pkg home) _) node uses =
|
||||
if pkg == here && nodeHasDebug node then
|
||||
Set.insert home uses
|
||||
else
|
||||
uses
|
||||
|
||||
|
||||
nodeHasDebug :: Opt.Node -> Bool
|
||||
nodeHasDebug node =
|
||||
case node of
|
||||
Opt.Define expr _ -> hasDebug expr
|
||||
Opt.DefineTailFunc _ expr _ -> hasDebug expr
|
||||
Opt.Ctor _ _ _ -> False
|
||||
Opt.Enum _ _ -> False
|
||||
Opt.Box _ -> False
|
||||
Opt.Link _ -> False
|
||||
Opt.Cycle defs _ -> any (hasDebug . snd) defs
|
||||
Opt.Manager _ -> False
|
||||
Opt.Kernel _ _ -> False
|
||||
Opt.PortIncoming expr _ -> hasDebug expr
|
||||
Opt.PortOutgoing expr _ -> hasDebug expr
|
||||
|
||||
|
||||
hasDebug :: Opt.Expr -> Bool
|
||||
hasDebug expression =
|
||||
case expression of
|
||||
Opt.Bool _ -> False
|
||||
Opt.Chr _ -> False
|
||||
Opt.Str _ -> False
|
||||
Opt.Int _ -> False
|
||||
Opt.Float _ -> False
|
||||
Opt.VarLocal _ -> False
|
||||
Opt.VarGlobal _ -> False
|
||||
Opt.VarEnum _ _ -> False
|
||||
Opt.VarBox _ -> False
|
||||
Opt.VarCycle _ _ -> False
|
||||
Opt.VarDebug _ _ _ _ -> True
|
||||
Opt.VarKernel _ _ -> False
|
||||
Opt.List exprs -> any hasDebug exprs
|
||||
Opt.Function _ expr -> hasDebug expr
|
||||
Opt.Call e es -> hasDebug e || any hasDebug es
|
||||
Opt.TailCall _ args -> any (hasDebug . snd) args
|
||||
Opt.If conds finally -> any (\(c,e) -> hasDebug c || hasDebug e) conds || hasDebug finally
|
||||
Opt.Let def body -> defHasDebug def || hasDebug body
|
||||
Opt.Destruct _ expr -> hasDebug expr
|
||||
Opt.Case _ _ d jumps -> deciderHasDebug d || any (hasDebug . snd) jumps
|
||||
Opt.Accessor _ -> False
|
||||
Opt.Access r _ -> hasDebug r
|
||||
Opt.Update r fs -> hasDebug r || any hasDebug fs
|
||||
Opt.Record fs -> any hasDebug fs
|
||||
Opt.Unit -> False
|
||||
Opt.Tuple a b c -> hasDebug a || hasDebug b || maybe False hasDebug c
|
||||
Opt.Shader _ -> False
|
||||
|
||||
|
||||
defHasDebug :: Opt.Def -> Bool
|
||||
defHasDebug def =
|
||||
case def of
|
||||
Opt.Def _ expr -> hasDebug expr
|
||||
Opt.TailDef _ _ expr -> hasDebug expr
|
||||
|
||||
|
||||
deciderHasDebug :: Opt.Decider Opt.Choice -> Bool
|
||||
deciderHasDebug decider =
|
||||
case decider of
|
||||
Opt.Leaf (Opt.Inline expr) -> hasDebug expr
|
||||
Opt.Leaf (Opt.Jump _) -> False
|
||||
Opt.Chain _ success failure -> deciderHasDebug success || deciderHasDebug failure
|
||||
Opt.FanOut _ tests fallback -> any (deciderHasDebug . snd) tests || deciderHasDebug fallback
|
||||
|
||||
|
||||
|
||||
-- TODO: FIND GLOBALLY UNUSED DEFINITIONS?
|
||||
-- TODO: FIND PACKAGE USAGE STATS? (e.g. elm-lang/core = 142, author/project = 2, etc.)
|
@ -3,6 +3,7 @@
|
||||
module Generate.Output
|
||||
( generate
|
||||
, generateReplFile
|
||||
, noDebugUsesInPackage
|
||||
, Options(..)
|
||||
, Output(..)
|
||||
, output
|
||||
@ -31,6 +32,8 @@ import qualified File.Crawl as Crawl
|
||||
import qualified File.IO as IO
|
||||
import qualified Generate.Functions as Functions
|
||||
import qualified Generate.Html as Html
|
||||
import qualified Generate.Nitpick as Nitpick
|
||||
import qualified Reporting.Exit as Exit
|
||||
import qualified Reporting.Task as Task
|
||||
import qualified Stuff.Paths as Paths
|
||||
import Terminal.Args (Parser(..), suggestFiles)
|
||||
@ -55,19 +58,24 @@ generate options summary graph@(Crawl.Graph args _ _ _ _) =
|
||||
return ()
|
||||
|
||||
Args.Roots name names ->
|
||||
generateMonolith options summary graph (name:names)
|
||||
do objectGraph <- organize summary graph
|
||||
|
||||
case _mode options of
|
||||
Obj.Dev -> return ()
|
||||
Obj.Prod -> noDebugUses summary objectGraph
|
||||
|
||||
generateMonolith options summary objectGraph (name:names)
|
||||
|
||||
|
||||
|
||||
-- GENERATE MONOLITH
|
||||
|
||||
|
||||
generateMonolith :: Options -> Summary.Summary -> Crawl.Result -> [Module.Raw] -> Task.Task ()
|
||||
generateMonolith (Options mode target maybeOutput) summary@(Summary.Summary _ project _ ifaces _) graph rootNames =
|
||||
generateMonolith :: Options -> Summary.Summary -> Obj.Graph -> [Module.Raw] -> Task.Task ()
|
||||
generateMonolith (Options mode target maybeOutput) (Summary.Summary _ project _ ifaces _) graph rootNames =
|
||||
do let pkg = Project.getName project
|
||||
let roots = map (Module.Canonical pkg) rootNames
|
||||
objectGraph <- organize summary graph
|
||||
case Obj.generate mode target ifaces objectGraph roots of
|
||||
case Obj.generate mode target ifaces graph roots of
|
||||
Obj.None ->
|
||||
return ()
|
||||
|
||||
@ -145,6 +153,25 @@ loadPackageObj ( name, (version,_) ) =
|
||||
|
||||
|
||||
|
||||
-- NO DEBUG USES
|
||||
|
||||
|
||||
noDebugUses :: Summary.Summary -> Obj.Graph -> Task.Task ()
|
||||
noDebugUses (Summary.Summary _ project _ _ _) graph =
|
||||
case Nitpick.findDebugUses (Project.getName project) graph of
|
||||
[] ->
|
||||
return ()
|
||||
|
||||
m:ms ->
|
||||
Task.throw (Exit.CannotOptimizeDebug m ms)
|
||||
|
||||
|
||||
noDebugUsesInPackage :: Summary.Summary -> Crawl.Result -> Task.Task ()
|
||||
noDebugUsesInPackage summary graph =
|
||||
noDebugUses summary =<< organize summary graph
|
||||
|
||||
|
||||
|
||||
-- OUTPUT
|
||||
|
||||
|
||||
|
@ -9,6 +9,7 @@ module Reporting.Exit
|
||||
|
||||
|
||||
import qualified Data.ByteString.Builder as B
|
||||
import Data.Monoid ((<>))
|
||||
import System.IO (stderr)
|
||||
import qualified Text.PrettyPrint.ANSI.Leijen as P
|
||||
|
||||
@ -44,9 +45,10 @@ data Exit
|
||||
| Publish Publish.Exit
|
||||
| BadHttp String Http.Exit
|
||||
|
||||
-- install
|
||||
-- misc
|
||||
| NoSolution [Pkg.Name]
|
||||
| CannotMakeNothing
|
||||
| CannotOptimizeDebug Module.Raw [Module.Raw]
|
||||
|
||||
|
||||
|
||||
@ -162,3 +164,30 @@ toReport exit =
|
||||
, Help.reflow
|
||||
"However many files you give, I will create one JS file out of them."
|
||||
]
|
||||
|
||||
CannotOptimizeDebug m ms ->
|
||||
Help.report "DEBUG REMNANTS" Nothing
|
||||
"There are uses of the `Debug` module in the following modules:"
|
||||
[ P.indent 4 $ P.red $ P.vcat $ map (P.text . Module.nameToString) (m:ms)
|
||||
, Help.reflow "But the --optimize flag only works if all `Debug` functions are removed!"
|
||||
, toSimpleNote $
|
||||
"The issue is that --optimize strips out info needed by `Debug` functions.\
|
||||
\ Here are two examples:"
|
||||
, P.indent 4 $ Help.reflow $
|
||||
"(1) It shortens record field names. This makes the generated JavaScript is\
|
||||
\ smaller, but `Debug.toString` cannot know the real field names anymore."
|
||||
, P.indent 4 $ Help.reflow $
|
||||
"(2) Values like `type Height = Height Float` are unboxed. This reduces\
|
||||
\ allocation, but it also means that `Debug.toString` cannot tell if it is\
|
||||
\ looking at a `Height` or `Float` value."
|
||||
, Help.reflow $
|
||||
"There are a few other cases like that, and it will be much worse once we start\
|
||||
\ inlining code. That optimization could move `Debug.log` and `Debug.todo` calls,\
|
||||
\ resulting in unpredictable behavior. I hope that clarifies why this restriction\
|
||||
\ exists!"
|
||||
]
|
||||
|
||||
|
||||
toSimpleNote :: String -> P.Doc
|
||||
toSimpleNote message =
|
||||
P.fillSep ((P.underline "Note" <> ":") : map P.text (words message))
|
||||
|
Loading…
Reference in New Issue
Block a user