on d163289 help sign in
elk/src/Reporting/Error/Syntax.hs
353 lines 11 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 | CommentAfterAnnotation String
20 | CommentOnComment
21 | CommentOnNothing
23 | SettingsOnNormalModule
24 | SettingsOnForeignModule
25 | DuplicateSettingOnEffectModule String
26 | BadSettingOnEffectModule String
28 | InfixDuplicate String
29 | TypeWithoutDefinition String
30 | DuplicateFieldName String
31 | DuplicateValueDeclaration String
32 | DuplicateTypeDeclaration String
33 | DuplicateDefinition String
34 | UnboundTypeVarsInUnion String [String] [String]
35 | UnboundTypeVarsInAlias String [String] [String]
36 | UnusedTypeVarsInAlias String [String] [String]
37 | MessyTypeVarsInAlias String [String] [String] [String]
40-- TO REPORT
42toReport :: RenderType.Localizer -> Error -> Report.Report
43toReport _localizer err =
44 case err of
45 Parse messages ->
46 parseErrorReport messages
48 BadFunctionName arity ->
49 Report.report
50 "BAD FUNCTION DEFINITION"
51 Nothing
52 "Complex patterns cannot be used as function names."
53 ( Help.reflowParagraph $
54 "This function definition has " ++ show arity ++ " arguments, but instead\
55 \ of a normal name like `add` or `reverse` it has a pattern. There is no\
56 \ way to \"deconstruct\" a function with pattern matching, so this needs to\
57 \ be changed to a normal name."
58 )
60 BadPattern name ->
61 Report.report
62 "BAD PATTERN"
63 Nothing
64 ("The free variable `" ++ name ++ "` appears more than once in this pattern.")
65 ( Help.stack
66 [ text "Rename the variables so there are no duplicates."
67 , Help.reflowParagraph $
68 "In Elm, pattern matching works by binding these free variables to subsections\
69 \ of the matching value. It does not make sense to have the same name for two\
70 \ different subsections though! When you say `" ++ name ++ "` in some code, there\
71 \ is no way for me to know which subsection you mean!"
72 ]
73 )
75 CommentAfterAnnotation name ->
76 Report.report
77 "STRAY COMMENT"
78 Nothing
79 ("The type annotation for `" ++ name ++ "` is followed by a documentation comment.")
80 ( Help.reflowParagraph $
81 "Documentation comments need to be *before* type annotations. Furthermore,\
82 \ type annotations need to be right above the corresponding definition, no\
83 \ comments in between."
84 )
86 CommentOnComment ->
87 Report.report
88 "STRAY COMMENT"
89 Nothing
90 ("This comment is followed by another documentation comment.")
91 ( Help.reflowParagraph $
92 "All documentation comments need to be right above the declaration they\
93 \ describe. Maybe some code got deleted or commented out by accident?"
94 )
96 CommentOnNothing ->
97 Report.report
98 "STRAY COMMENT"
99 Nothing
100 ("This documentation comment is not followed by anything.")
101 ( text "What is it documenting? Maybe it should just be removed?" )
103 InfixDuplicate opName ->
104 Report.report
105 "INFIX OVERLAP"
106 Nothing
107 ("The infix declarations for " ++ Help.functionName opName ++ " must be removed.")
108 ( Help.reflowParagraph $
109 "The precedence and associativity of a particular operator can only be set in\
110 \ one place in a code base, and " ++ Help.functionName opName
111 ++ " has already been set somewhere else."
112 )
114 TypeWithoutDefinition valueName ->
115 Report.report
116 "MISSING DEFINITION"
117 Nothing
118 ("There is a type annotation for `" ++ valueName ++ "` but there"
119 ++ " is no corresponding definition!"
120 )
121 ( text $
122 "Directly below the type annotation, put a definition like:\n\n"
123 ++ " " ++ valueName ++ " = 42"
124 )
126 DuplicateFieldName name ->
127 Report.report
128 "DUPLICATE FIELD"
129 Nothing
130 ("This record has more than one field named `" ++ name ++ "`.")
131 (text "There can only be one. Do some renaming to make sure the names are distinct!")
133 DuplicateValueDeclaration name ->
134 Report.report
135 "DUPLICATE DEFINITION"
136 Nothing
137 ( "Naming multiple top-level values `" ++ name ++ "` makes things\n"
138 ++ "ambiguous. When you say `" ++ name ++ "` which one do you want?"
139 )
140 ( Help.reflowParagraph $
141 "Find all the top-level values named " ++ Help.functionName name
142 ++ " and do some renaming. Make sure the names are distinct!"
143 )
145 DuplicateTypeDeclaration name ->
146 Report.report
147 "DUPLICATE DEFINITION"
148 Nothing
149 ( "There are multiple types named `" ++ name ++ "` in this module."
150 )
151 ( Help.reflowParagraph $
152 "Search through this module, find all the types named `" ++ name
153 ++ "`, and give each of them a unique name."
154 )
156 DuplicateDefinition name ->
157 Report.report
158 "DUPLICATE DEFINITION"
159 Nothing
160 ("There are multiple values named `" ++ name ++ "` in this let-expression."
161 )
162 ( Help.reflowParagraph $
163 "Search through this let-expression, find all the values named `" ++ name
164 ++ "`, and give each of them a unique name."
165 )
167 UnboundTypeVarsInUnion typeName givenVars unbound ->
168 unboundTypeVars "type" typeName givenVars unbound
170 UnboundTypeVarsInAlias typeName givenVars unbound ->
171 unboundTypeVars "type alias" typeName givenVars unbound
173 UnusedTypeVarsInAlias typeName givenVars unused ->
174 Report.report
175 "UNUSED TYPE VARIABLES"
176 Nothing
177 ( "Type alias `" ++ typeName ++ "` cannot have unused type variables: "
178 ++ Help.commaSep unused
179 )
180 ( Help.stack
181 [ text "You probably need to change the declaration like this:"
182 , dullyellow $ hsep $
183 map text ("type" : "alias" : typeName : filter (`notElem` unused) givenVars ++ ["=", "..."])
184 ]
185 )
187 MessyTypeVarsInAlias typeName givenVars unused unbound ->
188 Report.report
189 "TYPE VARIABLE PROBLEMS"
190 Nothing
191 ( "Type alias `" ++ typeName ++ "` has some problems with type variables."
192 )
193 ( Help.stack
194 [ Help.reflowParagraph $
195 "The declaration says it uses certain type variables ("
196 ++ Help.commaSep unused ++ ") but they do not appear in the aliased type. "
197 ++ "Furthermore, the aliased type says it uses type variables ("
198 ++ Help.commaSep unbound
199 ++ ") that do not appear in the declaration."
200 , text "You probably need to change the declaration like this:"
201 , dullyellow $ hsep $
202 map text ("type" : "alias" : typeName : filter (`notElem` unused) givenVars ++ unbound ++ ["=", "..."])
203 ]
204 )
207unboundTypeVars :: String -> String -> [String] -> [String] -> Report.Report
208unboundTypeVars declKind typeName givenVars unboundVars =
209 Report.report
210 "UNBOUND TYPE VARIABLES"
211 Nothing
212 ( Help.capitalize declKind ++ " `" ++ typeName ++ "` must declare its use of type variable"
213 ++ Help.commaSep unboundVars
214 )
215 ( Help.stack
216 [ text "You probably need to change the declaration like this:"
217 , hsep $
218 map text (declKind : typeName : givenVars)
219 ++ map (dullyellow . text) unboundVars
220 ++ map text ["=", "..."]
221 , Help.reflowParagraph $
222 "Here's why. Imagine one `" ++ typeName ++ "` where `" ++ head unboundVars ++
223 "` is an Int and another where it is a Bool. When we explicitly list the type\
224 \ variables, type checker can see that they are actually different types."
225 ]
226 )
230-- TAGGING PARSE ERRORS
233newline :: String
234newline = "NEWLINE"
237freshLine :: String
238freshLine = "FRESH_LINE"
241whitespace :: String
242whitespace = "WHITESPACE"
245tab :: String
246tab = "TAB"
249keyword :: String -> String
250keyword kwd =
251 "KEYWORD=" ++ kwd
254data SpecialMessage
255 = MsgKeyword String
256 | MsgTab
259extractSpecialMessage :: String -> Maybe SpecialMessage
260extractSpecialMessage message =
261 if List.isPrefixOf "KEYWORD=" message then
262 Just $ MsgKeyword (drop (length "KEYWORD=") message)
264 else if tab == message then
265 Just MsgTab
267 else
268 Nothing
271-- REPORTING PARSE ERRORS
273parseErrorReport :: [Parsec.Message] -> Report.Report
274parseErrorReport messages =
275 let
276 addMsg message hint =
277 case message of
278 Parsec.SysUnExpect _msg ->
279 hint
281 Parsec.UnExpect _msg ->
282 hint
284 Parsec.Expect msg ->
285 let
286 msg' =
287 if msg `elem` [whitespace, newline, freshLine] then
288 "whitespace"
289 else
290 msg
291 in
292 if msg == tab then
293 -- Our parser looks for tab so it can throw a custom tab error message.
294 -- Exclude tabs from the list of expected tokens so that no one thinks they are supported.
295 hint
296 else
297 hint { _expected = Set.insert msg' (_expected hint) }
299 Parsec.Message msg ->
300 hint { _messages = msg : _messages hint }
302 (ParseHint msgs expects) =
303 foldr addMsg emptyHint messages
305 (preHint, maybePost) =
306 case msgs of
307 [msg] ->
308 case extractSpecialMessage msg of
309 Just (MsgKeyword kwd) ->
310 ( "It looks like the keyword `" ++ kwd ++ "` is being used as a variable."
311 , Just "Rename it to something else."
312 )
314 Just MsgTab ->
315 ( "A tab character was found, but all whitespace (including indentation) must be spaces not tabs."
316 , Just "I am looking for spaces, not tabs."
317 )
319 Nothing ->
320 ( msg
321 , Nothing
322 )
324 _ ->
325 ( "I ran into something unexpected when parsing your code!"
326 , Nothing
327 )
329 postHint =
330 if Set.null expects then
331 "Maybe <http://elm-lang.org/docs/syntax> can help you figure it out."
333 else
334 "I am looking for one of the following things:\n"
335 ++ concatMap ("\n "++) (Set.toList expects)
336 in
337 Report.report
338 "SYNTAX PROBLEM"
339 Nothing
340 preHint
341 (text (maybe postHint id maybePost))
344data ParseHint = ParseHint
345 { _messages :: [String]
346 , _expected :: Set.Set String
347 }
348 deriving (Show)
351emptyHint :: ParseHint
352emptyHint =
353 ParseHint [] Set.empty