on master help sign in
Make sure that Debug module is not used in --optimize mode
6be8d82Evan Czaplicki
builder/src/Elm/Project.hs+1
@@ -86,4 +86,5 @@ generateDocs summary@(Summary.Summary root project _ _ _) =
86 (dirty, ifaces) <- Plan.plan (Just docsPath) summary graph
86 (dirty, ifaces) <- Plan.plan (Just docsPath) summary graph
87 answers <- Compile.compile project (Just docsPath) ifaces dirty
87 answers <- Compile.compile project (Just docsPath) ifaces dirty
88 results <- Artifacts.ignore answers
88 results <- Artifacts.ignore answers
89+ Output.noDebugUsesInPackage summary graph
89 Artifacts.writeDocs results docsPath
90 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 @@
3module Generate.Output
3module Generate.Output
4 ( generate
4 ( generate
5 , generateReplFile
5 , generateReplFile
6+ , noDebugUsesInPackage
6 , Options(..)
7 , Options(..)
7 , Output(..)
8 , Output(..)
8 , output
9 , output
@@ -31,6 +32,8 @@ import qualified File.Crawl as Crawl
31import qualified File.IO as IO
32import qualified File.IO as IO
32import qualified Generate.Functions as Functions
33import qualified Generate.Functions as Functions
33import qualified Generate.Html as Html
34import qualified Generate.Html as Html
35+import qualified Generate.Nitpick as Nitpick
36+import qualified Reporting.Exit as Exit
34import qualified Reporting.Task as Task
37import qualified Reporting.Task as Task
35import qualified Stuff.Paths as Paths
38import qualified Stuff.Paths as Paths
36import Terminal.Args (Parser(..), suggestFiles)
39import Terminal.Args (Parser(..), suggestFiles)
@@ -55,19 +58,24 @@ generate options summary graph@(Crawl.Graph args _ _ _ _) =
55 return ()
58 return ()
56
59
57 Args.Roots name names ->
60 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)
59
68
60
69
61
70
62-- GENERATE MONOLITH
71-- GENERATE MONOLITH
63
72
64
73
65−generateMonolith :: Options -> Summary.Summary -> Crawl.Result -> [Module.Raw] -> Task.Task ()
74+generateMonolith :: Options -> Summary.Summary -> Obj.Graph -> [Module.Raw] -> Task.Task ()
66−generateMonolith (Options mode target maybeOutput) summary@(Summary.Summary _ project _ ifaces _) graph rootNames =
75+generateMonolith (Options mode target maybeOutput) (Summary.Summary _ project _ ifaces _) graph rootNames =
67 do let pkg = Project.getName project
76 do let pkg = Project.getName project
68 let roots = map (Module.Canonical pkg) rootNames
77 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
71 Obj.None ->
79 Obj.None ->
72 return ()
80 return ()
73
81
@@ -145,6 +153,25 @@ loadPackageObj ( name, (version,_) ) =
145
153
146
154
147
155
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+
148-- OUTPUT
175-- OUTPUT
149
176
150
177
builder/src/Reporting/Exit.hs+30−1
@@ -9,6 +9,7 @@ module Reporting.Exit
9
9
10
10
11import qualified Data.ByteString.Builder as B
11import qualified Data.ByteString.Builder as B
12+import Data.Monoid ((<>))
12import System.IO (stderr)
13import System.IO (stderr)
13import qualified Text.PrettyPrint.ANSI.Leijen as P
14import qualified Text.PrettyPrint.ANSI.Leijen as P
14
15
@@ -44,9 +45,10 @@ data Exit
44 | Publish Publish.Exit
45 | Publish Publish.Exit
45 | BadHttp String Http.Exit
46 | BadHttp String Http.Exit
46
47
47− -- install
48+ -- misc
48 | NoSolution [Pkg.Name]
49 | NoSolution [Pkg.Name]
49 | CannotMakeNothing
50 | CannotMakeNothing
51+ | CannotOptimizeDebug Module.Raw [Module.Raw]
50
52
51
53
52
54
@@ -162,3 +164,30 @@ toReport exit =
162 , Help.reflow
164 , Help.reflow
163 "However many files you give, I will create one JS file out of them."
165 "However many files you give, I will create one JS file out of them."
164 ]
166 ]
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
108 File.Plan,
108 File.Plan,
109 Generate.Functions,
109 Generate.Functions,
110 Generate.Html,
110 Generate.Html,
111+ Generate.Nitpick,
111 Generate.Output,
112 Generate.Output,
112 Reporting.Exit,
113 Reporting.Exit,
113 Reporting.Exit.Assets,
114 Reporting.Exit.Assets,