on ef280ed help sign in
elk/src/Reporting/Error/Syntax.hs
351 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
18 | InfixDuplicate String
19 | TypeWithoutDefinition String
20 | PortWithoutAnnotation String
21 | UnexpectedPort
22 | DuplicateFieldName String
23 | DuplicateValueDeclaration String
24 | DuplicateTypeDeclaration String
25 | DuplicateDefinition String
26 | UnboundTypeVarsInUnion String [String] [String]
27 | UnboundTypeVarsInAlias String [String] [String]
28 | UnusedTypeVarsInAlias String [String] [String]
29 | MessyTypeVarsInAlias String [String] [String] [String]
30 | RepeatedArgument String
33-- TO REPORT
35toReport :: RenderType.Localizer -> Error -> Report.Report
36toReport _localizer err =
37 case err of
38 Parse messages ->
39 parseErrorReport messages
41 BadFunctionName arity ->
42 Report.report
43 "BAD FUNCTION DEFINITION"
44 Nothing
45 "Complex patterns cannot be used as function names."
46 ( Help.reflowParagraph $
47 "This function definition has " ++ show arity ++ " arguments, but instead\
48 \ of a normal name like `add` or `reverse` it has a pattern. There is no\
49 \ way to \"deconstruct\" a function with pattern matching, so this needs to\
50 \ be changed to a normal name."
51 )
53 BadPattern name ->
54 Report.report
55 "BAD PATTERN"
56 Nothing
57 ("The free variable `" ++ name ++ "` appears more than once in this pattern.")
58 ( Help.stack
59 [ text "Rename the variables so there are no duplicates."
60 , Help.reflowParagraph $
61 "In Elm, pattern matching works by binding these free variables to subsections\
62 \ of the matching value. It does not make sense to have the same name for two\
63 \ different subsections though! When you say `" ++ name ++ "` in some code, there\
64 \ is no way for me to know which subsection you mean!"
65 ]
66 )
68 InfixDuplicate opName ->
69 Report.report
70 "INFIX OVERLAP"
71 Nothing
72 ("The infix declarations for " ++ Help.functionName opName ++ " must be removed.")
73 ( Help.reflowParagraph $
74 "The precedence and associativity of a particular operator can only be set in\
75 \ one place in a code base, and " ++ Help.functionName opName
76 ++ " has already been set somewhere else."
77 )
79 TypeWithoutDefinition valueName ->
80 Report.report
81 "MISSING DEFINITION"
82 Nothing
83 ("There is a type annotation for `" ++ valueName ++ "` but there"
84 ++ " is no corresponding definition!"
85 )
86 ( text $
87 "Directly below the type annotation, put a definition like:\n\n"
88 ++ " " ++ valueName ++ " = 42"
89 )
91 PortWithoutAnnotation portName ->
92 Report.report
93 "PORT ERROR"
94 Nothing
95 ("Port `" ++ portName ++ "` does not have a type annotation!")
96 ( text $
97 "Directly above the port definition, I need something like this:\n\n"
98 ++ " port " ++ portName ++ " : Signal Int"
99 )
102 UnexpectedPort ->
103 Report.report
104 "PORT ERROR"
105 Nothing
106 "This module has ports, but ports can only appear in the main module."
107 ( Help.reflowParagraph $
108 "Ports in library code would create hidden dependencies where importing a\
109 \ module could bring in constraints not captured in the public API. Furthermore,\
110 \ if the module is imported twice, do we send values out the port twice?"
111 )
113 DuplicateFieldName name ->
114 Report.report
115 "DUPLICATE FIELD"
116 Nothing
117 ("This record has more than one field named `" ++ name ++ "`.")
118 (text "There can only be one. Do some renaming to make sure the names are distinct!")
120 DuplicateValueDeclaration name ->
121 Report.report
122 "DUPLICATE DEFINITION"
123 Nothing
124 ( "Naming multiple top-level values `" ++ name ++ "` makes things\n"
125 ++ "ambiguous. When you say `" ++ name ++ "` which one do you want?"
126 )
127 ( Help.reflowParagraph $
128 "Find all the top-level values named " ++ Help.functionName name
129 ++ " and do some renaming. Make sure the names are distinct!"
130 )
132 DuplicateTypeDeclaration name ->
133 Report.report
134 "DUPLICATE DEFINITION"
135 Nothing
136 ( "There are multiple types named `" ++ name ++ "` in this module."
137 )
138 ( Help.reflowParagraph $
139 "Search through this module, find all the types named `" ++ name
140 ++ "`, and give each of them a unique name."
141 )
143 DuplicateDefinition name ->
144 Report.report
145 "DUPLICATE DEFINITION"
146 Nothing
147 ("There are multiple values named `" ++ name ++ "` in this let-expression."
148 )
149 ( Help.reflowParagraph $
150 "Search through this let-expression, find all the values named `" ++ name
151 ++ "`, and give each of them a unique name."
152 )
154 UnboundTypeVarsInUnion typeName givenVars unbound ->
155 unboundTypeVars "type" typeName givenVars unbound
157 UnboundTypeVarsInAlias typeName givenVars unbound ->
158 unboundTypeVars "type alias" typeName givenVars unbound
160 UnusedTypeVarsInAlias typeName givenVars unused ->
161 Report.report
162 "UNUSED TYPE VARIABLES"
163 Nothing
164 ( "Type alias `" ++ typeName ++ "` cannot have unused type variables: "
165 ++ Help.commaSep unused
166 )
167 ( Help.stack
168 [ text "You probably need to change the declaration like this:"
169 , dullyellow $ hsep $
170 map text ("type" : "alias" : typeName : filter (`notElem` unused) givenVars ++ ["=", "..."])
171 ]
172 )
174 MessyTypeVarsInAlias typeName givenVars unused unbound ->
175 Report.report
176 "TYPE VARIABLE PROBLEMS"
177 Nothing
178 ( "Type alias `" ++ typeName ++ "` has some problems with type variables."
179 )
180 ( Help.stack
181 [ Help.reflowParagraph $
182 "The declaration says it uses certain type variables ("
183 ++ Help.commaSep unused ++ ") but they do not appear in the aliased type. "
184 ++ "Furthermore, the aliased type says it uses type variables ("
185 ++ Help.commaSep unbound
186 ++ ") that do not appear in the declaration."
187 , text "You probably need to change the declaration like this:"
188 , dullyellow $ hsep $
189 map text ("type" : "alias" : typeName : filter (`notElem` unused) givenVars ++ unbound ++ ["=", "..."])
190 ]
191 )
193 RepeatedArgument name ->
194 Report.report
195 "FUNCTION ARGUMENT REPEATED"
196 Nothing
197 ( "The argument `" ++ name ++ "` is defined multiple times."
198 )
199 ( Help.reflowParagraph $
200 "You should rename one of the occurrences of `" ++ name
201 ++ "` so that each argument is defined only once."
202 )
205unboundTypeVars :: String -> String -> [String] -> [String] -> Report.Report
206unboundTypeVars declKind typeName givenVars unboundVars@(tvar:_) =
207 Report.report
208 "UNBOUND TYPE VARIABLES"
209 Nothing
210 ( Help.capitalize declKind ++ " `" ++ typeName ++ "` must declare its use of type variable"
211 ++ Help.commaSep unboundVars
212 )
213 ( Help.stack
214 [ text "You probably need to change the declaration like this:"
215 , hsep $
216 map text (declKind : typeName : givenVars)
217 ++ map (dullyellow . text) unboundVars
218 ++ map text ["=", "..."]
219 , Help.reflowParagraph $
220 "Here's why. Imagine one `" ++ typeName ++ "` where `" ++ tvar ++ "` is an Int and\
221 \ another where it is a Bool. When we explicitly list the type variables, type\
222 \ checker can see that they are actually different types."
223 ]
224 )
228-- TAGGING PARSE ERRORS
231newline :: String
232newline = "NEWLINE"
235freshLine :: String
236freshLine = "FRESH_LINE"
239whitespace :: String
240whitespace = "WHITESPACE"
243tab :: String
244tab = "TAB"
247keyword :: String -> String
248keyword kwd =
249 "KEYWORD=" ++ kwd
252data SpecialMessage
253 = MsgKeyword String
254 | MsgTab
257extractSpecialMessage :: String -> Maybe SpecialMessage
258extractSpecialMessage message =
259 if List.isPrefixOf "KEYWORD=" message then
260 Just $ MsgKeyword (drop (length "KEYWORD=") message)
262 else if tab == message then
263 Just MsgTab
265 else
266 Nothing
269-- REPORTING PARSE ERRORS
271parseErrorReport :: [Parsec.Message] -> Report.Report
272parseErrorReport messages =
273 let
274 addMsg message hint =
275 case message of
276 Parsec.SysUnExpect _msg ->
277 hint
279 Parsec.UnExpect _msg ->
280 hint
282 Parsec.Expect msg ->
283 let
284 msg' =
285 if msg `elem` [whitespace, newline, freshLine] then
286 "whitespace"
287 else
288 msg
289 in
290 if msg == tab then
291 -- Our parser looks for tab so it can throw a custom tab error message.
292 -- Exclude tabs from the list of expected tokens so that no one thinks they are supported.
293 hint
294 else
295 hint { _expected = Set.insert msg' (_expected hint) }
297 Parsec.Message msg ->
298 hint { _messages = msg : _messages hint }
300 (ParseHint msgs expects) =
301 foldr addMsg emptyHint messages
303 (preHint, maybePost) =
304 case msgs of
305 [msg] ->
306 case extractSpecialMessage msg of
307 Just (MsgKeyword kwd) ->
308 ( "It looks like the keyword `" ++ kwd ++ "` is being used as a variable."
309 , Just "Rename it to something else."
310 )
312 Just MsgTab ->
313 ( "A tab character was found, but all whitespace (including indentation) must be spaces not tabs."
314 , Just "I am looking for spaces, not tabs."
315 )
317 Nothing ->
318 ( msg
319 , Nothing
320 )
322 _ ->
323 ( "I ran into something unexpected when parsing your code!"
324 , Nothing
325 )
327 postHint =
328 if Set.null expects then
329 "Maybe <http://elm-lang.org/docs/syntax> can help you figure it out."
331 else
332 "I am looking for one of the following things:\n"
333 ++ concatMap ("\n "++) (Set.toList expects)
334 in
335 Report.report
336 "SYNTAX PROBLEM"
337 Nothing
338 preHint
339 (text (maybe postHint id maybePost))
342data ParseHint = ParseHint
343 { _messages :: [String]
344 , _expected :: Set.Set String
345 }
346 deriving (Show)
349emptyHint :: ParseHint
350emptyHint =
351 ParseHint [] Set.empty