on 5b5230c help sign in
elk/src/Reporting/Error/Syntax.hs
430 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 | InfixDuplicate String
30 | TypeWithoutDefinition String
32 | DuplicateArgument String String
33 | DuplicateFieldName 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 InfixDuplicate opName ->
167 Report.report
168 "INFIX OVERLAP"
169 Nothing
170 ("The infix declarations for " ++ Help.functionName opName ++ " must be removed.")
171 ( Help.reflowParagraph $
172 "The precedence and associativity of a particular operator can only be set in\
173 \ one place in a code base, and " ++ Help.functionName opName
174 ++ " has already been set somewhere else."
175 )
177 TypeWithoutDefinition valueName ->
178 Report.report
179 "MISSING DEFINITION"
180 Nothing
181 ("There is a type annotation for `" ++ valueName ++ "` but there"
182 ++ " is no corresponding definition!"
183 )
184 ( text $
185 "Directly below the type annotation, put a definition like:\n\n"
186 ++ " " ++ valueName ++ " = 42"
187 )
189 DuplicateArgument funcName argName ->
190 Report.report
191 "DUPLICATE ARGUMENT"
192 Nothing
193 ( "The name `" ++ argName
194 ++ "` is used more than once in the arguments of `"
195 ++ funcName ++ "`."
196 )
197 ( Help.reflowParagraph $
198 "Rename things until `" ++ argName ++ "` is used only once.\
199 \ Otherwise how can we tell which one you want when you\
200 \ say `" ++ argName ++ "` it in the body of your function?"
201 )
203 DuplicateFieldName name ->
204 Report.report
205 "DUPLICATE FIELD"
206 Nothing
207 ("This record has more than one field named `" ++ name ++ "`.")
208 (text "There can only be one. Do some renaming to make sure the names are distinct!")
210 DuplicateValueDeclaration name ->
211 Report.report
212 "DUPLICATE DEFINITION"
213 Nothing
214 ( "Naming multiple top-level values `" ++ name ++ "` makes things\n"
215 ++ "ambiguous. When you say `" ++ name ++ "` which one do you want?"
216 )
217 ( Help.reflowParagraph $
218 "Find all the top-level values named " ++ Help.functionName name
219 ++ " and do some renaming. Make sure the names are distinct!"
220 )
222 DuplicateTypeDeclaration name ->
223 Report.report
224 "DUPLICATE DEFINITION"
225 Nothing
226 ( "There are multiple types named `" ++ name ++ "` in this module."
227 )
228 ( Help.reflowParagraph $
229 "Search through this module, find all the types named `" ++ name
230 ++ "`, and give each of them a unique name."
231 )
233 DuplicateDefinition name ->
234 Report.report
235 "DUPLICATE DEFINITION"
236 Nothing
237 ("There are multiple values named `" ++ name ++ "` in this let-expression."
238 )
239 ( Help.reflowParagraph $
240 "Search through this let-expression, find all the values named `" ++ name
241 ++ "`, and give each of them a unique name."
242 )
244 UnboundTypeVarsInUnion typeName givenVars unbound ->
245 unboundTypeVars "type" typeName givenVars unbound
247 UnboundTypeVarsInAlias typeName givenVars unbound ->
248 unboundTypeVars "type alias" typeName givenVars unbound
250 UnusedTypeVarsInAlias typeName givenVars unused ->
251 Report.report
252 "UNUSED TYPE VARIABLES"
253 Nothing
254 ( "Type alias `" ++ typeName ++ "` cannot have unused type variables: "
255 ++ Help.commaSep unused
256 )
257 ( Help.stack
258 [ text "You probably need to change the declaration like this:"
259 , dullyellow $ hsep $
260 map text ("type" : "alias" : typeName : filter (`notElem` unused) givenVars ++ ["=", "..."])
261 ]
262 )
264 MessyTypeVarsInAlias typeName givenVars unused unbound ->
265 Report.report
266 "TYPE VARIABLE PROBLEMS"
267 Nothing
268 ( "Type alias `" ++ typeName ++ "` has some problems with type variables."
269 )
270 ( Help.stack
271 [ Help.reflowParagraph $
272 "The declaration says it uses certain type variables ("
273 ++ Help.commaSep unused ++ ") but they do not appear in the aliased type. "
274 ++ "Furthermore, the aliased type says it uses type variables ("
275 ++ Help.commaSep unbound
276 ++ ") that do not appear in the declaration."
277 , text "You probably need to change the declaration like this:"
278 , dullyellow $ hsep $
279 map text ("type" : "alias" : typeName : filter (`notElem` unused) givenVars ++ unbound ++ ["=", "..."])
280 ]
281 )
284unboundTypeVars :: String -> String -> [String] -> [String] -> Report.Report
285unboundTypeVars declKind typeName givenVars unboundVars =
286 Report.report
287 "UNBOUND TYPE VARIABLES"
288 Nothing
289 ( Help.capitalize declKind ++ " `" ++ typeName ++ "` must declare its use of type variable"
290 ++ Help.commaSep unboundVars
291 )
292 ( Help.stack
293 [ text "You probably need to change the declaration like this:"
294 , hsep $
295 map text (declKind : typeName : givenVars)
296 ++ map (dullyellow . text) unboundVars
297 ++ map text ["=", "..."]
298 , Help.reflowParagraph $
299 "Here's why. Imagine one `" ++ typeName ++ "` where `" ++ head unboundVars ++
300 "` is an Int and another where it is a Bool. When we explicitly list the type\
301 \ variables, the type checker can see that they are actually different types."
302 ]
303 )
307-- TAGGING PARSE ERRORS
310newline :: String
311newline = "NEWLINE"
314freshLine :: String
315freshLine = "FRESH_LINE"
318whitespace :: String
319whitespace = "WHITESPACE"
322tab :: String
323tab = "TAB"
326keyword :: String -> String
327keyword kwd =
328 "KEYWORD=" ++ kwd
331data SpecialMessage
332 = MsgKeyword String
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 tab == message then
342 Just MsgTab
344 else
345 Nothing
348-- REPORTING PARSE ERRORS
350parseErrorReport :: [Parsec.Message] -> Report.Report
351parseErrorReport messages =
352 let
353 addMsg message hint =
354 case message of
355 Parsec.SysUnExpect _msg ->
356 hint
358 Parsec.UnExpect _msg ->
359 hint
361 Parsec.Expect msg ->
362 let
363 msg' =
364 if msg `elem` [whitespace, newline, freshLine] then
365 "whitespace"
366 else
367 msg
368 in
369 if msg == tab then
370 -- Our parser looks for tab so it can throw a custom tab error message.
371 -- Exclude tabs from the list of expected tokens so that no one thinks they are supported.
372 hint
373 else
374 hint { _expected = Set.insert msg' (_expected hint) }
376 Parsec.Message msg ->
377 hint { _messages = msg : _messages hint }
379 (ParseHint msgs expects) =
380 foldr addMsg emptyHint messages
382 (preHint, maybePost) =
383 case msgs of
384 [msg] ->
385 case extractSpecialMessage msg of
386 Just (MsgKeyword kwd) ->
387 ( "It looks like the keyword `" ++ kwd ++ "` is being used as a variable."
388 , Just "Rename it to something else."
389 )
391 Just MsgTab ->
392 ( "A tab character was found, but all whitespace (including indentation) must be spaces not tabs."
393 , Just "I am looking for spaces, not tabs."
394 )
396 Nothing ->
397 ( msg
398 , Nothing
399 )
401 _ ->
402 ( "I ran into something unexpected when parsing your code!"
403 , Nothing
404 )
406 postHint =
407 if Set.null expects then
408 "Maybe <http://elm-lang.org/docs/syntax> can help you figure it out."
410 else
411 "I am looking for one of the following things:\n"
412 ++ concatMap ("\n "++) (Set.toList expects)
413 in
414 Report.report
415 "SYNTAX PROBLEM"
416 Nothing
417 preHint
418 (text (maybe postHint id maybePost))
421data ParseHint = ParseHint
422 { _messages :: [String]
423 , _expected :: Set.Set String
424 }
425 deriving (Show)
428emptyHint :: ParseHint
429emptyHint =
430 ParseHint [] Set.empty