on be1239a help sign in
elk/src/Reporting/Error/Syntax.hs
283 lines 8.8 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 | UnboundTypeVarsInAlias String [String] String [String]
27 | UnboundTypeVarsInUnion String [String] String [String]
30-- TO REPORT
32toReport :: RenderType.Localizer -> Error -> Report.Report
33toReport _localizer err =
34 case err of
35 Parse messages ->
36 parseErrorReport messages
38 BadFunctionName arity ->
39 Report.report
40 "BAD FUNCTION DEFINITION"
41 Nothing
42 "Complex patterns cannot be used as function names."
43 ( Help.reflowParagraph $
44 "This function definition has " ++ show arity ++ " arguments, but instead\
45 \ of a normal name like `add` or `reverse` it has a pattern. There is no\
46 \ way to \"deconstruct\" a function with pattern matching, so this needs to\
47 \ be changed to a normal name."
48 )
50 BadPattern name ->
51 Report.report
52 "BAD PATTERN"
53 Nothing
54 ("The free variable `" ++ name ++ "` appears more than once in this pattern.")
55 ( Help.stack
56 [ text "Rename the variables so there are no duplicates."
57 , Help.reflowParagraph $
58 "In Elm, pattern matching works by binding these free variables to subsections\
59 \ of the matching value. It does not make sense to have the same name for two\
60 \ different subsections though! When you say `" ++ name ++ "` in some code, there\
61 \ is no way for me to know which subsection you mean!"
62 ]
63 )
65 InfixDuplicate opName ->
66 Report.report
67 "INFIX OVERLAP"
68 Nothing
69 ("The infix declarations for " ++ Help.functionName opName ++ " must be removed.")
70 ( Help.reflowParagraph $
71 "The precedence and associativity of a particular operator can only be set in\
72 \ one place in a code base, and " ++ Help.functionName opName
73 ++ " has already been set somewhere else."
74 )
76 TypeWithoutDefinition valueName ->
77 Report.report
78 "MISSING DEFINITION"
79 Nothing
80 ("There is a type annotation for `" ++ valueName ++ "` but there"
81 ++ " is no corresponding definition!"
82 )
83 ( text $
84 "Directly below the type annotation, put a definition like:\n\n"
85 ++ " " ++ valueName ++ " = 42"
86 )
88 PortWithoutAnnotation portName ->
89 Report.report
90 "PORT ERROR"
91 Nothing
92 ("Port `" ++ portName ++ "` does not have a type annotation!")
93 ( text $
94 "Directly above the port definition, I need something like this:\n\n"
95 ++ " port " ++ portName ++ " : Signal Int"
96 )
99 UnexpectedPort ->
100 Report.report
101 "PORT ERROR"
102 Nothing
103 "This module has ports, but ports can only appear in the main module."
104 ( Help.reflowParagraph $
105 "Ports in library code would create hidden dependencies where importing a\
106 \ module could bring in constraints not captured in the public API. Furthermore,\
107 \ if the module is imported twice, do we send values out the port twice?"
108 )
110 DuplicateFieldName name ->
111 Report.report
112 "DUPLICATE FIELD"
113 Nothing
114 ("This record has more than one field named `" ++ name ++ "`.")
115 (text "There can only be one. Do some renaming to make sure the names are distinct!")
117 DuplicateValueDeclaration name ->
118 Report.report
119 "DUPLICATE DEFINITION"
120 Nothing
121 ( "Naming multiple top-level values `" ++ name ++ "` makes things\n"
122 ++ "ambiguous. When you say `" ++ name ++ "` which one do you want?"
123 )
124 ( Help.reflowParagraph $
125 "Find all the top-level values named " ++ Help.functionName name
126 ++ " and do some renaming. Make sure the names are distinct!"
127 )
129 DuplicateTypeDeclaration name ->
130 Report.report
131 "DUPLICATE DEFINITION"
132 Nothing
133 ( "There are multiple types named `" ++ name ++ "` in this module."
134 )
135 ( Help.reflowParagraph $
136 "Search through this module, find all the types named `" ++ name
137 ++ "`, and give each of them a unique name."
138 )
140 DuplicateDefinition name ->
141 Report.report
142 "DUPLICATE DEFINITION"
143 Nothing
144 ("There are multiple values named `" ++ name ++ "` in this let-expression."
145 )
146 ( Help.reflowParagraph $
147 "Search through this let-expression, find all the values named `" ++ name
148 ++ "`, and give each of them a unique name."
149 )
151 UnboundTypeVarsInAlias typeName givenVars tvar tvars ->
152 unboundTypeVars "type alias" typeName givenVars tvar tvars
154 UnboundTypeVarsInUnion typeName givenVars tvar tvars ->
155 unboundTypeVars "type" typeName givenVars tvar tvars
159unboundTypeVars :: String -> String -> [String] -> String -> [String] -> Report.Report
160unboundTypeVars declKind typeName givenVars tvar tvars =
161 Report.report
162 "UNBOUND TYPE VARIABLES"
163 Nothing
164 ( Help.capitalize declKind ++ " `" ++ typeName ++ "` must declare its use of type variable"
165 ++ Help.commaSep (tvar:tvars)
166 )
167 ( Help.stack
168 [ text "You probably need to change the declaration like this:"
169 , hsep $
170 map text (declKind : typeName : givenVars)
171 ++ map (dullyellow . text) (tvar : tvars)
172 ++ map text ["=", "…"]
173 , Help.reflowParagraph $
174 "Here's why. Imagine one `" ++ typeName ++ "` where `" ++ tvar ++ "` is an Int and\
175 \ another where it is a Bool. When we explicitly list the type variables, type\
176 \ checker can see that they are actually different types."
177 ]
178 )
182-- TAGGING PARSE ERRORS
185newline :: String
186newline = "NEWLINE"
189freshLine :: String
190freshLine = "FRESH_LINE"
193whitespace :: String
194whitespace = "WHITESPACE"
197keyword :: String -> String
198keyword kwd =
199 "KEYWORD=" ++ kwd
202unkeyword :: String -> Maybe String
203unkeyword message =
204 if List.isPrefixOf "KEYWORD=" message then
205 Just (drop (length "KEYWORD=") message)
207 else
208 Nothing
211-- REPORTING PARSE ERRORS
213parseErrorReport :: [Parsec.Message] -> Report.Report
214parseErrorReport messages =
215 let
216 addMsg message hint =
217 case message of
218 Parsec.SysUnExpect _msg ->
219 hint
221 Parsec.UnExpect _msg ->
222 hint
224 Parsec.Expect msg ->
225 let
226 msg' =
227 if msg `elem` [whitespace, newline, freshLine] then
228 "whitespace"
229 else
230 msg
231 in
232 hint { _expected = Set.insert msg' (_expected hint) }
234 Parsec.Message msg ->
235 hint { _messages = msg : _messages hint }
237 (ParseHint msgs expects) =
238 foldr addMsg emptyHint messages
240 (preHint, maybePost) =
241 case msgs of
242 [msg] ->
243 case unkeyword msg of
244 Just kwd ->
245 ( "It looks like the keyword `" ++ kwd ++ "` is being used as a variable."
246 , Just "Rename it to something else."
247 )
249 Nothing ->
250 ( msg
251 , Nothing
252 )
254 _ ->
255 ( "I ran into something unexpected when parsing your code!"
256 , Nothing
257 )
259 postHint =
260 if Set.null expects then
261 "Maybe <http://elm-lang.org/docs/syntax> can help you figure it out."
263 else
264 "I am looking for one of the following things:\n"
265 ++ concatMap ("\n "++) (Set.toList expects)
266 in
267 Report.report
268 "SYNTAX PROBLEM"
269 Nothing
270 preHint
271 (text (maybe postHint id maybePost))
274data ParseHint = ParseHint
275 { _messages :: [String]
276 , _expected :: Set.Set String
277 }
278 deriving (Show)
281emptyHint :: ParseHint
282emptyHint =
283 ParseHint [] Set.empty