on master help sign in
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”

commit6be8d82c67f881a0c0d42f5e614b04cb51d87ae1 authorEvan Czaplicki <evancz@users.noreply.github.com> dateTue 3 Apr 2018, 18:54 (9 years ago) parentdac392a
5 files changed+163−6
builder/src/Elm/Project.hs+1 builder/src/Generate/Nitpick.hs+99 builder/src/Generate/Output.hs+32−5 builder/src/Reporting/Exit.hs+30−1 elm.cabal+1
builder/src/Elm/Project.hs+1
@@ -86,4 +86,5 @@ generateDocs summary@(Summary.Summary root project _ _ _) =
8686 (dirty, ifaces) <- Plan.plan (Just docsPath) summary graph
8787 answers <- Compile.compile project (Just docsPath) ifaces dirty
8888 results <- Artifacts.ignore answers
89+ Output.noDebugUsesInPackage summary graph
8990 Artifacts.writeDocs results docsPath
builder/src/Generate/Nitpick.hsnew file+99
@@ -0,0 +1,99 @@
1+module Generate.Nitpick
2+ ( findDebugUses
3+ )
4+ where
5+
6+
7+import qualified Data.Map as Map
8+import qualified Data.Set as Set
9+
10+import qualified AST.Optimized as Opt
11+import qualified AST.Module.Name as ModuleName
12+import qualified Elm.Package as Pkg
13+import qualified Elm.Name as N
14+
15+
16+
17+-- FIND DEBUG USES
18+
19+
20+findDebugUses :: Pkg.Name -> Opt.Graph -> [N.Name]
21+findDebugUses pkg (Opt.Graph _ graph _) =
22+ Set.toList $ Map.foldrWithKey (addDebugUses pkg) Set.empty graph
23+
24+
25+addDebugUses :: Pkg.Name -> Opt.Global -> Opt.Node -> Set.Set N.Name -> Set.Set N.Name
26+addDebugUses here (Opt.Global (ModuleName.Canonical pkg home) _) node uses =
27+ if pkg == here && nodeHasDebug node then
28+ Set.insert home uses
29+ else
30+ uses
31+
32+
33+nodeHasDebug :: Opt.Node -> Bool
34+nodeHasDebug node =
35+ case node of
36+ Opt.Define expr _ -> hasDebug expr
37+ Opt.DefineTailFunc _ expr _ -> hasDebug expr
38+ Opt.Ctor _ _ _ -> False
39+ Opt.Enum _ _ -> False
40+ Opt.Box _ -> False
41+ Opt.Link _ -> False
42+ Opt.Cycle defs _ -> any (hasDebug . snd) defs
43+ Opt.Manager _ -> False
44+ Opt.Kernel _ _ -> False
45+ Opt.PortIncoming expr _ -> hasDebug expr
46+ Opt.PortOutgoing expr _ -> hasDebug expr
47+
48+
49+hasDebug :: Opt.Expr -> Bool
50+hasDebug expression =
51+ case expression of
52+ Opt.Bool _ -> False
53+ Opt.Chr _ -> False
54+ Opt.Str _ -> False
55+ Opt.Int _ -> False
56+ Opt.Float _ -> False
57+ Opt.VarLocal _ -> False
58+ Opt.VarGlobal _ -> False
59+ Opt.VarEnum _ _ -> False
60+ Opt.VarBox _ -> False
61+ Opt.VarCycle _ _ -> False
62+ Opt.VarDebug _ _ _ _ -> True
63+ Opt.VarKernel _ _ -> False
64+ Opt.List exprs -> any hasDebug exprs
65+ Opt.Function _ expr -> hasDebug expr
66+ Opt.Call e es -> hasDebug e || any hasDebug es
67+ Opt.TailCall _ args -> any (hasDebug . snd) args
68+ Opt.If conds finally -> any (\(c,e) -> hasDebug c || hasDebug e) conds || hasDebug finally
69+ Opt.Let def body -> defHasDebug def || hasDebug body
70+ Opt.Destruct _ expr -> hasDebug expr
71+ Opt.Case _ _ d jumps -> deciderHasDebug d || any (hasDebug . snd) jumps
72+ Opt.Accessor _ -> False
73+ Opt.Access r _ -> hasDebug r
74+ Opt.Update r fs -> hasDebug r || any hasDebug fs
75+ Opt.Record fs -> any hasDebug fs
76+ Opt.Unit -> False
77+ Opt.Tuple a b c -> hasDebug a || hasDebug b || maybe False hasDebug c
78+ Opt.Shader _ -> False
79+
80+
81+defHasDebug :: Opt.Def -> Bool
82+defHasDebug def =
83+ case def of
84+ Opt.Def _ expr -> hasDebug expr
85+ Opt.TailDef _ _ expr -> hasDebug expr
86+
87+
88+deciderHasDebug :: Opt.Decider Opt.Choice -> Bool
89+deciderHasDebug decider =
90+ case decider of
91+ Opt.Leaf (Opt.Inline expr) -> hasDebug expr
92+ Opt.Leaf (Opt.Jump _) -> False
93+ Opt.Chain _ success failure -> deciderHasDebug success || deciderHasDebug failure
94+ Opt.FanOut _ tests fallback -> any (deciderHasDebug . snd) tests || deciderHasDebug fallback
95+
96+
97+
98+-- TODO: FIND GLOBALLY UNUSED DEFINITIONS?
99+-- TODO: FIND PACKAGE USAGE STATS? (e.g. elm-lang/core = 142, author/project = 2, etc.)
builder/src/Generate/Output.hs+32−5
@@ -3,6 +3,7 @@
33module Generate.Output
44 ( generate
55 , generateReplFile
6+ , noDebugUsesInPackage
67 , Options(..)
78 , Output(..)
89 , output
@@ -31,6 +32,8 @@ import qualified File.Crawl as Crawl
3132import qualified File.IO as IO
3233import qualified Generate.Functions as Functions
3334import qualified Generate.Html as Html
35+import qualified Generate.Nitpick as Nitpick
36+import qualified Reporting.Exit as Exit
3437import qualified Reporting.Task as Task
3538import qualified Stuff.Paths as Paths
3639import Terminal.Args (Parser(..), suggestFiles)
@@ -55,19 +58,24 @@ generate options summary graph@(Crawl.Graph args _ _ _ _) =
5558 return ()
5659
5760 Args.Roots name names ->
58− generateMonolith options summary graph (name:names)
61+ do objectGraph <- organize summary graph
62+
63+ case _mode options of
64+ Obj.Dev -> return ()
65+ Obj.Prod -> noDebugUses summary objectGraph
66+
67+ generateMonolith options summary objectGraph (name:names)
5968
6069
6170
6271-- GENERATE MONOLITH
6372
6473
65−generateMonolith :: Options -> Summary.Summary -> Crawl.Result -> [Module.Raw] -> Task.Task ()
66−generateMonolith (Options mode target maybeOutput) summary@(Summary.Summary _ project _ ifaces _) graph rootNames =
74+generateMonolith :: Options -> Summary.Summary -> Obj.Graph -> [Module.Raw] -> Task.Task ()
75+generateMonolith (Options mode target maybeOutput) (Summary.Summary _ project _ ifaces _) graph rootNames =
6776 do let pkg = Project.getName project
6877 let roots = map (Module.Canonical pkg) rootNames
69− objectGraph <- organize summary graph
70− case Obj.generate mode target ifaces objectGraph roots of
78+ case Obj.generate mode target ifaces graph roots of
7179 Obj.None ->
7280 return ()
7381
@@ -145,6 +153,25 @@ loadPackageObj ( name, (version,_) ) =
145153
146154
147155
156+-- NO DEBUG USES
157+
158+
159+noDebugUses :: Summary.Summary -> Obj.Graph -> Task.Task ()
160+noDebugUses (Summary.Summary _ project _ _ _) graph =
161+ case Nitpick.findDebugUses (Project.getName project) graph of
162+ [] ->
163+ return ()
164+
165+ m:ms ->
166+ Task.throw (Exit.CannotOptimizeDebug m ms)
167+
168+
169+noDebugUsesInPackage :: Summary.Summary -> Crawl.Result -> Task.Task ()
170+noDebugUsesInPackage summary graph =
171+ noDebugUses summary =<< organize summary graph
172+
173+
174+
148175-- OUTPUT
149176
150177
builder/src/Reporting/Exit.hs+30−1
@@ -9,6 +9,7 @@ module Reporting.Exit
99
1010
1111import qualified Data.ByteString.Builder as B
12+import Data.Monoid ((<>))
1213import System.IO (stderr)
1314import qualified Text.PrettyPrint.ANSI.Leijen as P
1415
@@ -44,9 +45,10 @@ data Exit
4445 | Publish Publish.Exit
4546 | BadHttp String Http.Exit
4647
47− -- install
48+ -- misc
4849 | NoSolution [Pkg.Name]
4950 | CannotMakeNothing
51+ | CannotOptimizeDebug Module.Raw [Module.Raw]
5052
5153
5254
@@ -162,3 +164,30 @@ toReport exit =
162164 , Help.reflow
163165 "However many files you give, I will create one JS file out of them."
164166 ]
167+
168+ CannotOptimizeDebug m ms ->
169+ Help.report "DEBUG REMNANTS" Nothing
170+ "There are uses of the `Debug` module in the following modules:"
171+ [ P.indent 4 $ P.red $ P.vcat $ map (P.text . Module.nameToString) (m:ms)
172+ , Help.reflow "But the --optimize flag only works if all `Debug` functions are removed!"
173+ , toSimpleNote $
174+ "The issue is that --optimize strips out info needed by `Debug` functions.\
175+ \ Here are two examples:"
176+ , P.indent 4 $ Help.reflow $
177+ "(1) It shortens record field names. This makes the generated JavaScript is\
178+ \ smaller, but `Debug.toString` cannot know the real field names anymore."
179+ , P.indent 4 $ Help.reflow $
180+ "(2) Values like `type Height = Height Float` are unboxed. This reduces\
181+ \ allocation, but it also means that `Debug.toString` cannot tell if it is\
182+ \ looking at a `Height` or `Float` value."
183+ , Help.reflow $
184+ "There are a few other cases like that, and it will be much worse once we start\
185+ \ inlining code. That optimization could move `Debug.log` and `Debug.todo` calls,\
186+ \ resulting in unpredictable behavior. I hope that clarifies why this restriction\
187+ \ exists!"
188+ ]
189+
190+
191+toSimpleNote :: String -> P.Doc
192+toSimpleNote message =
193+ P.fillSep ((P.underline "Note" <> ":") : map P.text (words message))
elm.cabal+1
@@ -108,6 +108,7 @@ Executable elm
108108 File.Plan,
109109 Generate.Functions,
110110 Generate.Html,
111+ Generate.Nitpick,
111112 Generate.Output,
112113 Reporting.Exit,
113114 Reporting.Exit.Assets,