on adcea86 help sign in
elk/src/Reporting/Error/Syntax.hs
448 lines 15 kB history raw
1{-# OPTIONS_GHC -Wall #-}
2module Reporting.Error.Syntax where
4import qualified Data.List as List
5import qualified Data.Set as Set
6import qualified Text.Parsec.Error as Parsec
7import Text.PrettyPrint.ANSI.Leijen (dullyellow, hsep, text)
9import qualified Reporting.Error.Helpers as Help
10import qualified Reporting.Render.Type as RenderType
11import qualified Reporting.Region as Region
12import qualified Reporting.Report as Report
15data Error
16 = Parse [Parsec.Message]
17 | BadFunctionName Int
18 | BadPattern String
20 | CommentOnNothing
22 | SettingsOnNormalModule
23 | SettingsOnPortModule
24 | DuplicateSettingOnEffectModule String
25 | BadSettingOnEffectModule String
26 | NoSettingsOnEffectModule
27 | MissingManagerOnEffectModule String
28 | UnexpectedPort String
30 | TypeWithoutDefinition String
32 | DuplicateArgument String String
33 | DuplicateFieldName Region.Region String
34 | DuplicateValueDeclaration String
35 | DuplicateTypeDeclaration String
36 | DuplicateDefinition String
37 | UnboundTypeVarsInUnion String [String] [String]
38 | UnboundTypeVarsInAlias String [String] [String]
39 | UnusedTypeVarsInAlias String [String] [String]
40 | MessyTypeVarsInAlias String [String] [String] [String]
43-- TO REPORT
45toReport :: RenderType.Localizer -> Error -> Report.Report
46toReport _localizer err =
47 case err of
48 Parse messages ->
49 parseErrorReport messages
51 BadFunctionName arity ->
52 Report.report
53 "BAD FUNCTION DEFINITION"
54 Nothing
55 "Complex patterns cannot be used as function names."
56 ( Help.reflowParagraph $
57 "This function definition has " ++ show arity ++ " arguments, but instead\
58 \ of a normal name like `add` or `reverse` it has a pattern. There is no\
59 \ way to \"deconstruct\" a function with pattern matching, so this needs to\
60 \ be changed to a normal name."
61 )
63 BadPattern name ->
64 Report.report
65 "BAD PATTERN"
66 Nothing
67 ("The free variable `" ++ name ++ "` appears more than once in this pattern.")
68 ( Help.stack
69 [ text "Rename the variables so there are no duplicates."
70 , Help.reflowParagraph $
71 "In Elm, pattern matching works by binding these free variables to subsections\
72 \ of the matching value. It does not make sense to have the same name for two\
73 \ different subsections though! When you say `" ++ name ++ "` in some code, there\
74 \ is no way for me to know which subsection you mean!"
75 ]
76 )
78 CommentOnNothing ->
79 Report.report
80 "STRAY COMMENT"
81 Nothing
82 ("This documentation comment is not followed by anything.")
83 ( Help.reflowParagraph $
84 "All documentation comments need to be right above the declaration they\
85 \ describe. Maybe some code got deleted or commented out by accident? Or\
86 \ maybe this comment is here by accident?"
87 )
89 SettingsOnNormalModule ->
90 Report.report
91 "BAD MODULE DECLARATION"
92 Nothing
93 "A normal module can expose values, but not settings like this."
94 ( Help.reflowParagraph $
95 "If you want a normal module, just remove this stuff. If you want to create\
96 \ an `effect module` you just forgot to use the `effect` keyword. In that\
97 \ case, just change `module` to `effect module` and you should be headed in\
98 \ the right direction!"
99 )
101 SettingsOnPortModule ->
102 Report.report
103 "BAD MODULE DECLARATION"
104 Nothing
105 "A port module can expose values, but not have settings like this."
106 ( Help.reflowParagraph $
107 "If you want a port module, just remove this stuff. If you want to create\
108 \ a custom effect module instead (which is rare) just change `port module`\
109 \ to `effect module` and you should be headed in the right direction!"
110 )
112 DuplicateSettingOnEffectModule name ->
113 Report.report
114 "EFFECT MODULE PROBLEM"
115 Nothing
116 ("You have defined " ++ Help.functionName name ++ " multiple times.")
117 ( Help.reflowParagraph $
118 "There can only be one " ++ Help.functionName name
119 ++ " though! Remove until there is one left."
120 )
122 BadSettingOnEffectModule name ->
123 Report.report
124 "EFFECT MODULE PROBLEM"
125 Nothing
126 ("Setting " ++ Help.functionName name ++ " is not recognized.")
127 ( Help.reflowParagraph $
128 "Remove this entry and you should be all set!"
129 )
131 NoSettingsOnEffectModule ->
132 Report.report
133 "EFFECT MODULE PROBLEM"
134 Nothing
135 ("You are defining an effect module, but it has no settings.")
136 ( Help.reflowParagraph $
137 "If you just wanted a normal module, change the keywords `effect module`\
138 \ to `module` and you should be all set. If you want a proper effect module,\
139 \ you need to specify your commands and/or subscriptions. Read more about this\
140 \ here: <http://guide.elm-lang.org/effect_managers/>"
141 )
143 MissingManagerOnEffectModule name ->
144 Report.report
145 "EFFECT MODULE PROBLEM"
146 Nothing
147 ("You are defining an effect module, but there is no " ++ Help.functionName name ++ " defined.")
148 ( Help.reflowParagraph $
149 "There is a small set of top-level functions and values that must be defined\
150 \ in any complete effect module. The best thing is probably to just read more\
151 \ about effect modules here:\
152 \ <http://guide.elm-lang.org/effect_managers/>"
153 )
155 UnexpectedPort name ->
156 Report.report
157 "BAD PORT"
158 Nothing
159 ("You are declaring port " ++ Help.functionName name ++ " in a normal module.")
160 ( Help.reflowParagraph $
161 "All ports must be defined in a `port module`. You should probably have just\
162 \ one of these for your project. This way all of your foreign interactions\
163 \ stay relatively organized."
164 )
166 TypeWithoutDefinition valueName ->
167 Report.report
168 "MISSING DEFINITION"
169 Nothing
170 ("There is a type annotation for `" ++ valueName ++ "` but there"
171 ++ " is no corresponding definition!"
172 )
173 ( text $
174 "Directly below the type annotation, put a definition like:\n\n"
175 ++ " " ++ valueName ++ " = 42"
176 )
178 DuplicateArgument funcName argName ->
179 Report.report
180 "DUPLICATE ARGUMENT"
181 Nothing
182 ( "The name `" ++ argName
183 ++ "` is used more than once in the arguments of `"
184 ++ funcName ++ "`."
185 )
186 ( Help.reflowParagraph $
187 "Rename things until `" ++ argName ++ "` is used only once.\
188 \ Otherwise how can we tell which one you want when you\
189 \ say `" ++ argName ++ "` it in the body of your function?"
190 )
192 DuplicateFieldName region name ->
193 Report.report
194 "DUPLICATE FIELD"
195 (Just region)
196 ("This record has more than one field named `" ++ name ++ "`.")
197 (text "There can only be one. Do some renaming to make sure the names are distinct!")
199 DuplicateValueDeclaration name ->
200 Report.report
201 "DUPLICATE DEFINITION"
202 Nothing
203 ( "Naming multiple top-level values `" ++ name ++ "` makes things\n"
204 ++ "ambiguous. When you say `" ++ name ++ "` which one do you want?"
205 )
206 ( Help.reflowParagraph $
207 "Find all the top-level values named " ++ Help.functionName name
208 ++ " and do some renaming. Make sure the names are distinct!"
209 )
211 DuplicateTypeDeclaration name ->
212 Report.report
213 "DUPLICATE DEFINITION"
214 Nothing
215 ( "There are multiple types named `" ++ name ++ "` in this module."
216 )
217 ( Help.reflowParagraph $
218 "Search through this module, find all the types named `" ++ name
219 ++ "`, and give each of them a unique name."
220 )
222 DuplicateDefinition name ->
223 Report.report
224 "DUPLICATE DEFINITION"
225 Nothing
226 ("There are multiple values named `" ++ name ++ "` in this let-expression."
227 )
228 ( Help.reflowParagraph $
229 "Search through this let-expression, find all the values named `" ++ name
230 ++ "`, and give each of them a unique name."
231 )
233 UnboundTypeVarsInUnion typeName givenVars unbound ->
234 unboundTypeVars "type" typeName givenVars unbound
236 UnboundTypeVarsInAlias typeName givenVars unbound ->
237 unboundTypeVars "type alias" typeName givenVars unbound
239 UnusedTypeVarsInAlias typeName givenVars unused ->
240 Report.report
241 "UNUSED TYPE VARIABLES"
242 Nothing
243 ( "Type alias `" ++ typeName ++ "` cannot have unused type variables: "
244 ++ Help.commaSep unused
245 )
246 ( Help.stack
247 [ text "You probably need to change the declaration like this:"
248 , dullyellow $ hsep $
249 map text ("type" : "alias" : typeName : filter (`notElem` unused) givenVars ++ ["=", "..."])
250 ]
251 )
253 MessyTypeVarsInAlias typeName givenVars unused unbound ->
254 Report.report
255 "TYPE VARIABLE PROBLEMS"
256 Nothing
257 ( "Type alias `" ++ typeName ++ "` has some problems with type variables."
258 )
259 ( Help.stack
260 [ Help.reflowParagraph $
261 "The declaration says it uses certain type variables ("
262 ++ Help.commaSep unused ++ ") but they do not appear in the aliased type. "
263 ++ "Furthermore, the aliased type says it uses type variables ("
264 ++ Help.commaSep unbound
265 ++ ") that do not appear in the declaration."
266 , text "You probably need to change the declaration like this:"
267 , dullyellow $ hsep $
268 map text ("type" : "alias" : typeName : filter (`notElem` unused) givenVars ++ unbound ++ ["=", "..."])
269 ]
270 )
273unboundTypeVars :: String -> String -> [String] -> [String] -> Report.Report
274unboundTypeVars declKind typeName givenVars unboundVars =
275 Report.report
276 "UNBOUND TYPE VARIABLES"
277 Nothing
278 ( Help.capitalize declKind ++ " `" ++ typeName ++ "` must declare its use of type variable"
279 ++ Help.commaSep unboundVars
280 )
281 ( Help.stack
282 [ text "You probably need to change the declaration like this:"
283 , hsep $
284 map text (declKind : typeName : givenVars)
285 ++ map (dullyellow . text) unboundVars
286 ++ map text ["=", "..."]
287 , Help.reflowParagraph $
288 "Here's why. Imagine one `" ++ typeName ++ "` where `" ++ head unboundVars ++
289 "` is an Int and another where it is a Bool. When we explicitly list the type\
290 \ variables, the type checker can see that they are actually different types."
291 ]
292 )
296-- TAGGING PARSE ERRORS
299newline :: String
300newline = "NEWLINE"
303freshLine :: String
304freshLine = "FRESH_LINE"
307whitespace :: String
308whitespace = "WHITESPACE"
311tab :: String
312tab = "TAB"
315singleQuotes :: String
316singleQuotes = "SINGLE_QUOTES"
319keyword :: String -> String
320keyword kwd =
321 "KEYWORD=" ++ kwd
324prime :: String -> String
325prime var =
326 "PRIME=" ++ var
329data SpecialMessage
330 = MsgKeyword String
331 | MsgPrime String
332 | MsgSingleQuotes
333 | MsgTab
336extractSpecialMessage :: String -> Maybe SpecialMessage
337extractSpecialMessage message =
338 if List.isPrefixOf "KEYWORD=" message then
339 Just $ MsgKeyword (drop (length "KEYWORD=") message)
341 else if List.isPrefixOf "PRIME=" message then
342 Just $ MsgPrime (drop (length "PRIME=") message)
344 else if tab == message then
345 Just MsgTab
347 else if singleQuotes == message then
348 Just MsgSingleQuotes
350 else
351 Nothing
354-- REPORTING PARSE ERRORS
356parseErrorReport :: [Parsec.Message] -> Report.Report
357parseErrorReport messages =
358 let
359 addMsg message hint =
360 case message of
361 Parsec.SysUnExpect _msg ->
362 hint
364 Parsec.UnExpect _msg ->
365 hint
367 Parsec.Expect msg ->
368 let
369 msg' =
370 if msg `elem` [whitespace, newline, freshLine] then
371 "whitespace"
372 else
373 msg
374 in
375 if msg == tab then
376 -- Our parser looks for tab so it can throw a custom tab error message.
377 -- Exclude tabs from the list of expected tokens so that no one thinks they are supported.
378 hint
379 else
380 hint { _expected = Set.insert msg' (_expected hint) }
382 Parsec.Message msg ->
383 hint { _messages = msg : _messages hint }
385 (ParseHint msgs expects) =
386 foldr addMsg emptyHint messages
388 (preHint, maybePost) =
389 case msgs of
390 [msg] ->
391 case extractSpecialMessage msg of
392 Just (MsgKeyword kwd) ->
393 ( "It looks like the keyword `" ++ kwd ++ "` is being used as a variable."
394 , Just "Rename it to something else."
395 )
397 Just (MsgPrime var) ->
398 ( "Ran into a single quote in a variable name. This was removed in 0.18!"
399 , Just $
400 "Change it to a number or an underscore, like " ++ var ++ "_ or " ++ var
401 ++ "1\n\nOr better yet, choose a more descriptive name!"
402 )
404 Just MsgTab ->
405 ( "A tab character was found, but all whitespace (including indentation) must be spaces not tabs."
406 , Just "I am looking for spaces, not tabs."
407 )
409 Just MsgSingleQuotes ->
410 ( "It likes you are trying to create a string with single quotes."
411 , Just "Switch from ' to \" and you should be all set!"
412 )
414 Nothing ->
415 ( msg
416 , Nothing
417 )
419 _ ->
420 ( "I ran into something unexpected when parsing your code!"
421 , Nothing
422 )
424 postHint =
425 if Set.null expects then
426 "Maybe <http://elm-lang.org/docs/syntax> can help you figure it out."
428 else
429 "I am looking for one of the following things:\n"
430 ++ concatMap ("\n "++) (Set.toList expects)
431 in
432 Report.report
433 "SYNTAX PROBLEM"
434 Nothing
435 preHint
436 (text (maybe postHint id maybePost))
439data ParseHint = ParseHint
440 { _messages :: [String]
441 , _expected :: Set.Set String
442 }
443 deriving (Show)
446emptyHint :: ParseHint
447emptyHint =
448 ParseHint [] Set.empty