on master help sign in
Add a hint for canonicalization errors

Fix https://github.com/elm-lang/package.elm-lang.org/issues/185 The idea is that when folks have name errors, maybe they are new to Elm and do not understand imports yet. Now they will get a nice link that explains exactly how import, exposing, and as work together. It also outlines the default imports. Also make ModuleNotFound point exactly to the module name

commit61e7508a9eb1da55061ce8a56b7fbb168e4a6a95 authorEvan Czaplicki <evancz@users.noreply.github.com> dateMon 10 Jul 2017, 22:37 (9 years ago) parent445ea07
11 files changed+207−65
hints/imports.md+115 src/AST/Module.hs+1−1 src/Canonicalize.hs+1−1 src/Canonicalize/Setup.hs+12−3 src/Elm/Compiler.hs+2−2 src/Parse/Kernel.hs+1−1 src/Parse/Module.hs+4−4 src/Reporting/Error/Canonicalize.hs+35−31 src/Reporting/Error/Docs.hs+1−3 src/Reporting/Error/Type.hs+2−12 src/Reporting/Helpers.hs+33−7
hints/imports.mdnew file+115
@@ -0,0 +1,115 @@
1+# Hints for Imports
2+
3+When getting started with Elm, it is pretty common to have questions about how the `import` declarations work exactly. These questions usually arise when you start playing with the `Html` library so we will focus on that.
4+
5+
6+## `import`
7+
8+An Elm file is called a **module**. To access code in other files, you need to `import` it!
9+
10+So say you want to use the [`div`](http://package.elm-lang.org/packages/elm-lang/html/latest/Html#div) function from the [`elm-lang/html`](http://package.elm-lang.org/packages/elm-lang/html/latest) package. The simplest way is to import it like this:
11+
12+```elm
13+import Html
14+
15+main =
16+ Html.div [] []
17+```
18+
19+After saying `import Html` we can refer to anything inside that module as long as it is *qualified*. This works for:
20+
21+ - **Values** &mdash; we can refer to `Html.text`, `Html.h1`, etc.
22+ - **Types** &mdash; We can refer to [`Attribute`](http://package.elm-lang.org/packages/elm-lang/html/latest/Html#Attribute) as `Html.Attribute`.
23+
24+So if we add a type annotation to `main` it would look like this:
25+
26+```elm
27+import Html
28+
29+main : Html.Html msg
30+main =
31+ Html.div [] []
32+```
33+
34+We are referring to the [`Html`](http://package.elm-lang.org/packages/elm-lang/html/latest/Html#Html) type, using its *qualified* name `Html.Html`. This can feel weird at first, but it starts feeling natural quite quickly!
35+
36+> **Note:** Modules do not contain other modules. So the `Html` module *does not* contain the `Html.Attributes` module. Those are separate names that happen to have some overlap. So if you say `import Html` you *do not* get access to `Html.Attributes.style`. You must `import Html.Attributes` module separately.
37+
38+
39+## `as`
40+
41+It is best practice to always use *qualified* names, but sometimes module names are so long that it becomes unweildy. This is common for the `Html.Attributes` module. We can use the `as` keyword to help with this:
42+
43+```elm
44+import Html
45+import Html.Attributes as A
46+
47+main =
48+ Html.div [ A.style "color" "red" ] [ Html.text "Hello!" ]
49+```
50+
51+Saying `import Html.Attributes as A` lets us refer to any value or type in `Html.Attributes` as long as it is qualified with an `A`. So now we can refer to [`style`](http://package.elm-lang.org/packages/elm-lang/html/latest/Html-Attributes#style) as `A.style`.
52+
53+
54+## `exposing`
55+
56+In quick drafts, maybe you want to use *unqualified* names. You can do that with the `exposing` keyword like this:
57+
58+```elm
59+import Html exposing (..)
60+import Html.Attributes exposing (style)
61+
62+main : Html msg
63+main =
64+ div [ style "color" "red" ] [ text "Hello!" ]
65+```
66+
67+Saying `import Html exposing (..)` means I can refer to any value or type from the `Html` module without qualification. Notice that I use the `Html` type, the `div` function, and the `text` function without qualification in the example above.
68+
69+> **Note:** It seems neat to expose types and values directly, but it can get out of hand. Say you `import` ten modules `exposing` all of their content. It quickly becomes difficult to figure out what is going on in your code. “Wait, where is this function from?” And then trying to sort through all the imports to find it. Point is, use `exposing (..)` sparingly!
70+
71+Saying `import Html.Attributes exposing (style)` is a bit more reasonable. It means I can refer to the `style` function without qualification, but that is it. You are still importing the `Html.Attributes` module like normal though, so you would say `Html.Attributes.class` or `Html.Attributes.id` to refer to other values and types from that module.
72+
73+
74+## `as` and `exposing`
75+
76+There is one last way to import a module. You can combine `as` and `exposing` to try to get a nice balance of qualified names:
77+
78+```elm
79+import Html exposing (Html, div, text)
80+import Html.Attributes as A exposing (style)
81+
82+main : Html msg
83+main =
84+ div [ A.class "greeting", style "color" "red" ] [ text "Hello!" ]
85+```
86+
87+Notice that I refer to `A.class` which is qualified and `style` which is unqualified.
88+
89+
90+## Default Imports
91+
92+We just learned all the variations of the `import` syntax in Elm. You will use some version of that syntay to `import` any module you ever write.
93+
94+It would be the best policy to make it so every module in the whole ecosystem works this way. We thought so in the past at least, but there are some modules that are so commonly used that the Elm compiler automatically adds the imports to every file. These default imports include:
95+
96+```elm
97+import Basics exposing (..)
98+import List exposing ( List, (::) )
99+import Maybe exposing ( Maybe( Just, Nothing ) )
100+import Result exposing ( Result( Ok, Err ) )
101+import String
102+import Tuple
103+
104+import Debug
105+
106+import Platform exposing ( Program )
107+import Platform.Cmd as Cmd exposing ( Cmd )
108+import Platform.Sub as Sub exposing ( Sub )
109+```
110+
111+You can think of these imports being at the top of any module you write.
112+
113+One could argue that `Maybe` fundamental to how we handle errors in Elm code that it is *basically* part of the language. One could also argue that it is extraordinarily annoying to have to import `Maybe` once you get past your first couple weeks with Elm. Either way, we know that default imports are not ideal in some sense, so we have tried to keep the default imports as minimal as possible.
114+
115+> **Note:** Elm performs dead code elimination, so if you do not use something from a module, it is not included in the generated code. So if you `import` a module with hundreds of functions, you do not need to worry about the size of your assets. You will only get what you use!
src/AST/Module.hs+1−1
@@ -133,7 +133,7 @@ type Optimized =
133133-- IMPORTS
134134
135135
136−type UserImport = A.Located (Name.Raw, ImportMethod)
136+type UserImport = A.Located (A.Located Name.Raw, ImportMethod)
137137
138138
139139type DefaultImport = (Name.Raw, ImportMethod)
src/Canonicalize.hs+1−1
@@ -112,7 +112,7 @@ filterImports
112112 -> Result [ModuleName.Raw]
113113filterImports uses (defaults, imports) =
114114 let
115− checkImport (A.A region (name, _method)) =
115+ checkImport (A.A region (A.A _ name, _method)) =
116116 if Set.member name uses then
117117 return (Just name)
118118
src/Canonicalize/Setup.hs+12−3
@@ -42,7 +42,7 @@ environment importDict interfaces (Module.Module name info) =
4242 info
4343
4444 allImports =
45− imports ++ map (A.A (error "default import not found")) defaults
45+ imports ++ map toPretendUserImport defaults
4646
4747 getImportPatches =
4848 traverse (importToPatches importDict interfaces) allImports
@@ -62,6 +62,15 @@ environment importDict interfaces (Module.Module name info) =
6262 addTypeAliases name typeAliasNodes env
6363
6464
65+toPretendUserImport :: Module.DefaultImport -> Module.UserImport
66+toPretendUserImport (name, method) =
67+ let
68+ errLoc =
69+ error "default import not found"
70+ in
71+ A.A errLoc ( A.A errLoc name, method )
72+
73+
6574
6675-- PATCHES FOR IMPORTS
6776
@@ -69,9 +78,9 @@ environment importDict interfaces (Module.Module name info) =
6978importToPatches
7079 :: Map.Map ModuleName.Raw ModuleName.Canonical
7180 -> Module.Interfaces
72− -> A.Located (ModuleName.Raw, Module.ImportMethod)
81+ -> Module.UserImport
7382 -> Result [Env.Patch]
74−importToPatches importDict allInterfaces (A.A region (rawImportName, method)) =
83+importToPatches importDict allInterfaces (A.A _ (A.A region rawImportName, method)) =
7584 let
7685 maybeInterface =
7786 do canonicalName <- Map.lookup rawImportName importDict
src/Elm/Compiler.hs+2−2
@@ -187,8 +187,8 @@ toHeaderSummary pkgName (Module.Header maybeHeaderDecl imports) =
187187 let
188188 dependencies =
189189 if pkgName == Package.core
190− then map (fst . A.drop) imports
191− else map (fst . A.drop) imports ++ map fst Imports.defaults
190+ then map (A.drop . fst . A.drop) imports
191+ else map (A.drop . fst . A.drop) imports ++ map fst Imports.defaults
192192 in
193193 case maybeHeaderDecl of
194194 Nothing ->
src/Parse/Kernel.hs+1−1
@@ -91,7 +91,7 @@ type Table =
9191
9292
9393importToTable :: Module.UserImport -> Table
94−importToTable (A.A _ ( fullName, Module.ImportMethod maybeAlias exposed )) =
94+importToTable (A.A _ ( A.A _ fullName, Module.ImportMethod maybeAlias exposed )) =
9595 let
9696 shortName =
9797 case maybeAlias of
src/Parse/Module.hs+4−4
@@ -164,7 +164,7 @@ chompImports imports =
164164 keyword "import"
165165 pushContext start E.Import
166166 spaces
167− name <- qualifiedCapVar
167+ name <- addLocation qualifiedCapVar
168168 end <- getPosition
169169 pos <- whitespace
170170 oneOf
@@ -182,7 +182,7 @@ chompImports imports =
182182 ]
183183
184184
185−chompAs :: R.Position -> Text -> [Module.UserImport] -> Parser [Module.UserImport]
185+chompAs :: R.Position -> A.Located Text -> [Module.UserImport] -> Parser [Module.UserImport]
186186chompAs start name imports =
187187 do keyword "as"
188188 spaces
@@ -199,7 +199,7 @@ chompAs start name imports =
199199 ]
200200
201201
202−chompExposing :: R.Position -> Text -> Maybe Text -> [Module.UserImport] -> Parser [Module.UserImport]
202+chompExposing :: R.Position -> A.Located Text -> Maybe Text -> [Module.UserImport] -> Parser [Module.UserImport]
203203chompExposing start name maybeAlias imports =
204204 do keyword "exposing"
205205 spaces
@@ -211,7 +211,7 @@ chompExposing start name maybeAlias imports =
211211 chompImports (userImport:imports)
212212
213213
214−method :: R.Position -> R.Position -> Text -> Maybe Text -> Exposing.Raw -> Module.UserImport
214+method :: R.Position -> R.Position -> A.Located Text -> Maybe Text -> Exposing.Raw -> Module.UserImport
215215method start end name maybeAlias exposed =
216216 A.at start end ( name, Module.ImportMethod maybeAlias exposed )
217217
src/Reporting/Error/Canonicalize.hs+35−31
@@ -133,41 +133,47 @@ port name tipe maybeMessage =
133133-- TO REPORT
134134
135135
136−namingError :: Text -> Text -> Report.Report
137−namingError pre post =
138− Report.report "NAMING ERROR" Nothing pre (text post)
139−
140−
141136toReport :: RenderType.Localizer -> Error -> Report.Report
142137toReport localizer err =
143138 case err of
144139 Var (VarError kind name problem suggestions) ->
145− let var = kind <> " `" <> name <> "`"
140+ let
141+ variableName =
142+ kind <> " `" <> name <> "`"
143+
144+ learnMore =
145+ Help.toHint $
146+ "New to Elm? Unsure exactly how `import` works? Learn all about it here: "
147+ <> Help.hintLink "imports"
148+
149+ namingError overview maybeStarter specializedSuggestions =
150+ Report.report "NAMING ERROR" Nothing overview $
151+ maybeYouWant maybeStarter specializedSuggestions (Just learnMore)
146152 in
147153 case problem of
148154 Ambiguous ->
149155 namingError
150− ("This usage of " <> var <> " is ambiguous.")
151− (maybeYouWant suggestions)
156+ ("This usage of " <> variableName <> " is ambiguous.")
157+ Nothing
158+ suggestions
152159
153160 UnknownQualifier qualifier localName ->
154161 namingError
155− ("Cannot find " <> var <> ".")
156− ( "No module called `" <> qualifier <> "` has been imported. "
157− <> maybeYouWant (map (\modul -> modul <> "." <> localName) suggestions)
158− )
162+ ("Cannot find " <> variableName)
163+ (Just $ text $ "No module called `" <> qualifier <> "` has been imported.")
164+ (map (\modul -> modul <> "." <> localName) suggestions)
159165
160166 QualifiedUnknown qualifier localName ->
161167 namingError
162− ("Cannot find " <> var <> ".")
163− ( "`" <> qualifier <> "` does not expose `" <> localName <> "`. "
164− <> maybeYouWant (map (\v -> qualifier <> "." <> v) suggestions)
165− )
168+ ("Cannot find " <> variableName)
169+ (Just $ text $ "`" <> qualifier <> "` does not expose `" <> localName <> "`.")
170+ (map (\v -> qualifier <> "." <> v) suggestions)
166171
167172 ExposedUnknown ->
168173 namingError
169− ("Cannot find " <> var)
170− (maybeYouWant suggestions)
174+ ("Cannot find " <> variableName)
175+ Nothing
176+ suggestions
171177
172178 BadRecursion region def defs ->
173179 case defs of
@@ -234,27 +240,25 @@ toReport localizer err =
234240 )
235241
236242 Import name importError ->
237− let moduleName = ModuleName.toText name
238− in
239243 case importError of
240244 ModuleNotFound suggestions ->
241− namingError
242− ("Could not find a module named `" <> moduleName <> "`")
243− (maybeYouWant suggestions)
245+ Report.report "IMPORT ERROR" Nothing
246+ ("Could not find a module named `" <> ModuleName.toText name <> "`")
247+ (maybeYouWant Nothing suggestions Nothing)
244248
245249 ValueNotFound value suggestions ->
246− namingError
247− ("Module `" <> moduleName <> "` does not expose `" <> value <> "`")
248− (maybeYouWant suggestions)
250+ Report.report "IMPORT ERROR" Nothing
251+ ("Module `" <> ModuleName.toText name <> "` does not expose `" <> value <> "`")
252+ (maybeYouWant Nothing suggestions Nothing)
249253
250254 Export name suggestions ->
251− namingError
252− ("Could not export `" <> name <> "` which is not defined in this module.")
253− (maybeYouWant suggestions)
255+ Report.report "EXPOSING ERROR" Nothing
256+ ("Could not expose `" <> name <> "` which is not defined in this module.")
257+ (maybeYouWant Nothing suggestions Nothing)
254258
255259 DuplicateExport name ->
256− namingError
257− ("You are trying to export `" <> name <> "` multiple times!")
260+ Report.report "EXPOSING ERROR" Nothing
261+ ("You are trying to expose `" <> name <> "` multiple times!")
258262 "Remove duplicates until there is only one listed."
259263
260264 Port (PortError name tipe maybeMessage) ->
src/Reporting/Error/Docs.hs+1−3
@@ -42,9 +42,7 @@ toReport err =
4242 Nothing
4343 ("Your module documentation includes `" <> name <> "` which is not exported."
4444 )
45− ( text $
46− "Is it misspelled? Should it be exported? "
47− <> Help.maybeYouWant suggestions
45+ ( Help.maybeYouWant (Just "Is it misspelled? Should it be exported?") suggestions Nothing
4846 )
4947
5048 OnlyInExports names ->
src/Reporting/Error/Type.hs+2−12
@@ -24,8 +24,8 @@ import qualified Reporting.Render.Type as RenderType
2424import qualified Reporting.Report as Report
2525import qualified Reporting.Helpers as Help
2626import Reporting.Helpers
27− ( Doc, (<>), (<+>), capitalize, dullyellow, fillSep, functionName
28− , i2t, indent, ordinalize, reflowParagraph, stack, text, underline, vcat
27+ ( Doc, (<>), (<+>), capitalize, dullyellow, functionName
28+ , i2t, indent, ordinalize, reflowParagraph, stack, text, toHint, vcat
2929 )
3030
3131
@@ -845,16 +845,6 @@ doubleRigidError maybeLeftName maybeRightName =
845845 weirdRigidError
846846
847847
848−hintDoc :: Doc
849−hintDoc =
850− underline (text "Hint") <> ":"
851−
852−
853−toHint :: Text -> Doc
854−toHint txt =
855− fillSep (hintDoc : map text (Text.words txt))
856−
857−
858848messyFieldsHelp :: [Text] -> [Text] -> [Text] -> Maybe (Text, [Doc])
859849messyFieldsHelp both leftOnly rightOnly =
860850 case (leftOnly, rightOnly) of
src/Reporting/Helpers.hs+33−7
@@ -8,7 +8,7 @@ module Reporting.Helpers
88 -- custom helpers
99 , i2t
1010 , functionName, args
11− , hintLink, stack, reflowParagraph
11+ , toHint, hintLink, stack, reflowParagraph
1212 , commaSep, capitalize, ordinalize, drawCycle
1313 , findPotentialTypos, findTypoPairs, vetTypos
1414 , nearbyNames, distance, maybeYouWant
@@ -72,6 +72,19 @@ args n =
7272 i2t n <> if n == 1 then " argument" else " arguments"
7373
7474
75+
76+-- HINTS
77+
78+
79+toHint :: Text -> Doc
80+toHint txt =
81+ let
82+ hint =
83+ underline (text "Hint") <> ":"
84+ in
85+ fillSep (hint : map text (Text.words txt))
86+
87+
7588hintLink :: Text -> Text
7689hintLink fileName =
7790 "<https://github.com/elm-lang/elm-compiler/blob/"
@@ -221,14 +234,27 @@ distance x y =
221234 (Text.unpack y)
222235
223236
224−maybeYouWant :: [Text] -> Text
225−maybeYouWant suggestions =
237+maybeYouWant :: Maybe Doc -> [Text] -> Maybe Doc -> Doc
238+maybeYouWant maybeStarter suggestions maybeEnder =
226239 case suggestions of
227240 [] ->
228− ""
241+ case (maybeStarter, maybeEnder) of
242+ (Nothing, Nothing) ->
243+ P.empty
244+
245+ (Just starter, Nothing) ->
246+ starter
247+
248+ (Nothing, Just ender) ->
249+ ender
250+
251+ (Just starter, Just ender) ->
252+ stack [ starter, ender ]
229253
230254 _:_ ->
231− "Maybe you want one of the following?\n"
232− <>
233− Text.concat (map ("\n " <>) (take 4 suggestions))
255+ stack $
256+ [ maybe id (<+>) maybeStarter "Maybe you want one of the following?"
257+ , P.indent 4 (P.vcat (map text (take 4 suggestions)))
258+ ]
259+ ++ maybe [] pure maybeEnder
234260