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