on master help sign in
Switch Reports to use Docs for pre and post hints

This means we can put all sorts of visual helpers (like colors) anywhere we want in a hint. This is the initial pass, getting all the code working in this new world. Next round will be to hone the usage of colors as I do more thorough testing of how everything displays. This also introduces the idea of linking to “hint” pages on certain errors where the error message could be kind of long.

commit656bfb74c40c7b29b749a0c2da39e2083a1d5226 authorEvan Czaplicki <info@elm-lang.org> dateWed 21 Oct 2015, 00:01 (11 years ago) parent55ba05f
16 files changed+885−758
elm-compiler.cabal+2 src/Elm/Compiler.hs+27−25 src/Generate/JavaScript/Port.hs+2−2 src/Reporting/Error.hs+5−12 src/Reporting/Error/Canonicalize.hs+87−40 src/Reporting/Error/Docs.hs+32−19 src/Reporting/Error/Helpers.hs+101−2 src/Reporting/Error/Pattern.hs+42−85 src/Reporting/Error/Syntax.hs+125−83 src/Reporting/Error/Type.hs+232−245 src/Reporting/Render/Code.hs+113 src/Reporting/Render/Type.hs+47−31 src/Reporting/Report.hs+50−195 src/Reporting/Warning.hs+16−15 src/Type/Constrain/Literal.hs+2−2 src/Validate.hs+2−2
elm-compiler.cabal+2
@@ -109,6 +109,8 @@ Library
109109 Reporting.Error.Syntax,
110110 Reporting.Error.Type,
111111 Reporting.Region,
112+ Reporting.Render.Code,
113+ Reporting.Render.Type,
112114 Reporting.Report,
113115 Reporting.Result,
114116 Reporting.Warning,
src/Elm/Compiler.hs+27−25
@@ -3,7 +3,7 @@ module Elm.Compiler
33 ( version
44 , parseDependencies
55 , compile, Context(..), Result(..)
6− , Dealiaser, dummyDealiaser
6+ , Localizer, dummyLocalizer
77 , Error, errorToString, errorToJson, printError
88 , Warning, warningToString, warningToJson, printWarning
99 )
@@ -11,6 +11,7 @@ module Elm.Compiler
1111
1212import qualified Data.Aeson as Json
1313import qualified Data.Map as Map
14+import System.IO (Handle)
1415
1516import qualified AST.Module as Module
1617import qualified AST.Module.Name as ModuleName
@@ -26,6 +27,7 @@ import qualified Parse.Parse as Parse
2627import qualified Reporting.Annotation as A
2728import qualified Reporting.Error as Error
2829import qualified Reporting.Render.Type as RenderType
30+import qualified Reporting.Report as Report
2931import qualified Reporting.Result as Result
3032import qualified Reporting.Warning as Warning
3133
@@ -65,7 +67,7 @@ compile
6567 :: Context
6668 -> String
6769 -> PublicModule.Interfaces
68− -> (Dealiaser, [Warning], Either [Error] Result)
70+ -> (Localizer, [Warning], Either [Error] Result)
6971
7072compile context source interfaces =
7173 let
@@ -81,7 +83,7 @@ compile context source interfaces =
8183
8284 return (Result docs interface javascript)
8385 in
84− ( maybe dummyDealiaser Dealiaser localizer
86+ ( maybe dummyLocalizer Localizer localizer
8587 , map Warning warnings
8688 , Result.destruct (Left . map Error) Right rawResult
8789 )
@@ -125,13 +127,13 @@ docsGen isExposed modul =
125127
126128-- DEALIASER
127129
128−newtype Dealiaser =
129− Dealiaser RenderType.Localizer
130+newtype Localizer =
131+ Localizer RenderType.Localizer
130132
131133
132−dummyDealiaser :: Dealiaser
133−dummyDealiaser =
134− Dealiaser Map.empty
134+dummyLocalizer :: Localizer
135+dummyLocalizer =
136+ Localizer Map.empty
135137
136138
137139-- ERRORS
@@ -140,18 +142,18 @@ newtype Error =
140142 Error (A.Located Error.Error)
141143
142144
143−errorToString :: Dealiaser -> String -> String -> Error -> String
144−errorToString (Dealiaser localizer) location source (Error err) =
145− Error.toString localizer location source err
145+errorToString :: Localizer -> String -> String -> Error -> String
146+errorToString (Localizer localizer) location source (Error (A.A region err)) =
147+ Report.toString location region (Error.toReport localizer err) source
146148
147149
148−printError :: Dealiaser -> String -> String -> Error -> IO ()
149−printError (Dealiaser localizer) location source (Error err) =
150− Error.print localizer location source err
150+printError :: Handle -> Localizer -> String -> String -> Error -> IO ()
151+printError handle (Localizer localizer) location source (Error (A.A region err)) =
152+ Report.toHandle handle location region (Error.toReport localizer err) source
151153
152154
153−errorToJson :: Dealiaser -> String -> Error -> Json.Value
154−errorToJson (Dealiaser localizer) location (Error err) =
155+errorToJson :: Localizer -> String -> Error -> Json.Value
156+errorToJson (Localizer localizer) location (Error err) =
155157 Error.toJson localizer location err
156158
157159
@@ -161,18 +163,18 @@ newtype Warning =
161163 Warning (A.Located Warning.Warning)
162164
163165
164−warningToString :: Dealiaser -> String -> String -> Warning -> String
165−warningToString (Dealiaser localizer) location source (Warning err) =
166− Warning.toString localizer location source err
166+warningToString :: Localizer -> String -> String -> Warning -> String
167+warningToString (Localizer localizer) location source (Warning (A.A region wrn)) =
168+ Report.toString location region (Warning.toReport localizer wrn) source
167169
168170
169−printWarning :: Dealiaser -> String -> String -> Warning -> IO ()
170−printWarning (Dealiaser localizer) location source (Warning err) =
171− Warning.print localizer location source err
171+printWarning :: Handle -> Localizer -> String -> String -> Warning -> IO ()
172+printWarning handle (Localizer localizer) location source (Warning (A.A region wrn)) =
173+ Report.toHandle handle location region (Warning.toReport localizer wrn) source
172174
173175
174−warningToJson :: Dealiaser -> String -> Warning -> Json.Value
175−warningToJson (Dealiaser localizer) location (Warning err) =
176− Warning.toJson localizer location err
176+warningToJson :: Localizer -> String -> Warning -> Json.Value
177+warningToJson (Localizer localizer) location (Warning wrn) =
178+ Warning.toJson localizer location wrn
177179
178180
src/Generate/JavaScript/Port.hs+2−2
@@ -97,14 +97,14 @@ inbound name portType =
9797 T.Normal tipe ->
9898 _Port "inbound" `call`
9999 [ StringLit () name
100− , StringLit () (RenderType.tipe Map.empty tipe)
100+ , StringLit () (show (RenderType.toDoc Map.empty tipe))
101101 , toTypeFunction tipe
102102 ]
103103
104104 T.Signal _root arg ->
105105 _Port "inboundSignal" `call`
106106 [ StringLit () name
107− , StringLit () (RenderType.tipe Map.empty arg)
107+ , StringLit () (show (RenderType.toDoc Map.empty arg))
108108 , toTypeFunction arg
109109 ]
110110
src/Reporting/Error.hs+5−12
@@ -4,7 +4,6 @@ module Reporting.Error where
44
55import Data.Aeson ((.=))
66import qualified Data.Aeson as Json
7−import Prelude hiding (print)
87
98import qualified Reporting.Annotation as A
109import qualified Reporting.Error.Canonicalize as Canonicalize
@@ -16,8 +15,10 @@ import qualified Reporting.Render.Type as RenderType
1615import qualified Reporting.Report as Report
1716
1817
18+
1919-- ALL POSSIBLE ERRORS
2020
21+
2122data Error
2223 = Syntax Syntax.Error
2324 | Canonicalize Canonicalize.Error
@@ -26,8 +27,10 @@ data Error
2627 | Docs Docs.Error
2728
2829
30+
2931-- TO REPORT
3032
33+
3134toReport :: RenderType.Localizer -> Error -> Report.Report
3235toReport localizer err =
3336 case err of
@@ -47,20 +50,10 @@ toReport localizer err =
4750 Docs.toReport docsError
4851
4952
50−-- TO STRING
51−
52−toString :: RenderType.Localizer -> String -> String -> A.Located Error -> String
53−toString localizer location source (A.A region err) =
54− Report.toString location region (toReport localizer err) source
55−
56−
57−print :: RenderType.Localizer -> String -> String -> A.Located Error -> IO ()
58−print localizer location source (A.A region err) =
59− Report.printError location region (toReport localizer err) source
60−
6153
6254-- TO JSON
6355
56+
6457toJson :: RenderType.Localizer -> FilePath -> A.Located Error -> Json.Value
6558toJson localizer filePath (A.A region err) =
6659 let
src/Reporting/Error/Canonicalize.hs+87−40
@@ -1,9 +1,13 @@
11{-# OPTIONS_GHC -Wall #-}
22module Reporting.Error.Canonicalize where
33
4+import Control.Arrow (second)
5+import Text.PrettyPrint.ANSI.Leijen (indent, text)
6+
47import qualified AST.Module.Name as ModuleName
58import qualified AST.Type as Type
69import qualified AST.Variable as Var
10+import qualified Reporting.Annotation as A
711import qualified Reporting.Error.Helpers as Help
812import qualified Reporting.Region as Region
913import qualified Reporting.Render.Type as RenderType
@@ -20,8 +24,10 @@ data Error
2024 | Port PortError
2125
2226
27+
2328-- VARIABLES
2429
30+
2531data VarError = VarError
2632 { varKind :: String
2733 , varName :: String
@@ -42,8 +48,10 @@ variable kind name problem suggestions =
4248 Var (VarError kind name problem suggestions)
4349
4450
51+
4552-- PATTERN
4653
54+
4755data PatternError
4856 = PatternArgMismatch Var.Canonical Int Int
4957
@@ -53,8 +61,10 @@ argMismatch name expected actual =
5361 Pattern (PatternArgMismatch name expected actual)
5462
5563
64+
5665-- IMPORTS
5766
67+
5868data ImportError
5969 = ModuleNotFound [ModuleName.Raw]
6070 | ValueNotFound String [String]
@@ -70,8 +80,10 @@ valueNotFound name value possibilities =
7080 Import name (ValueNotFound value possibilities)
7181
7282
83+
7384-- ALIASES
7485
86+
7587data AliasError
7688 = ArgMismatch Var.Canonical Int Int
7789 | SelfRecursive String [String] Type.Raw
@@ -83,8 +95,10 @@ alias name expected actual =
8395 Alias (ArgMismatch name expected actual)
8496
8597
98+
8699-- PORTS
87100
101+
88102data PortError = PortError
89103 { portName :: String
90104 , portIsInbound :: Bool
@@ -105,11 +119,13 @@ port name isInbound rootType localType maybeMessage =
105119 Port (PortError name isInbound rootType localType maybeMessage)
106120
107121
122+
108123-- TO REPORT
109124
125+
110126namingError :: String -> String -> Report.Report
111127namingError pre post =
112− Report.simple "NAMING ERROR" pre post
128+ Report.report "NAMING ERROR" Nothing pre (text post)
113129
114130
115131toReport :: RenderType.Localizer -> Error -> Report.Report
@@ -154,25 +170,37 @@ toReport localizer err =
154170 argMismatchReport "Type" var expected actual
155171
156172 SelfRecursive name tvars tipe ->
157− Report.simple "ALIAS PROBLEM" "This type alias is recursive, forming an infinite type!" $
158− "Type aliases are just names for more fundamental types, so I go through\n"
159− ++ "and expand them all to know the real underlying type. The problem is that\n"
160− ++ "when I expand a recursive type alias, it keeps getting bigger and bigger.\n"
161− ++ "Dealiasing results in an infinitely large type! Try this instead:\n\n"
162− ++ error "TODO RenderType.typeDecl localizer name tvars tipe"
163− ++ "\n\nThis creates a brand new type that cannot be expanded. It is similar types\n"
164− ++ "like List which are recursive, but do not get expanded into infinite types."
173+ Report.report
174+ "ALIAS PROBLEM"
175+ Nothing
176+ "This type alias is recursive, forming an infinite type!"
177+ (
178+ Help.stack
179+ [ text $
180+ "When I expand a recursive type alias, it just keeps getting bigger and bigger.\n"
181+ ++ "So dealiasing results in an infinitely large type! Try this instead:"
182+ , indent 4 $
183+ RenderType.decl localizer name tvars [(name, [unsafePromote tipe])]
184+ , text $
185+ "This creates a brand new type that cannot be expanded. Read more about this at:\n"
186+ ++ Help.hintLink "infinite-alias"
187+ ]
188+ )
165189
166190 MutuallyRecursive aliases ->
167− Report.simple "ALIAS PROBLEM" "This type alias is part of a mutually recursive set of type aliases." $
168− "The following type aliases all depend on each other in some way:\n"
169− ++ concatMap showName aliases ++ "\n\n"
170− ++ "The problem is that when you try to expand any of these type aliases, they\n"
171− ++ "expand infinitely! To fix, first try to centralize them into one alias."
172− where
173− showName (Region.Region start _, name, _, _) =
174− "\n " ++ name ++ replicate (65 - length name) ' '
175− ++ "line " ++ show (Region.line start)
191+ Report.report
192+ "ALIAS PROBLEM"
193+ Nothing
194+ "This type alias is part of a mutually recursive set of type aliases."
195+ ( Help.stack
196+ [ text "The following type aliases are mutually recursive:"
197+ , error "TODO visual loop" aliases
198+ , text $
199+ "You need to convert at least one `type alias` into a `type`. This is a kind of\n"
200+ ++ "subtle distinction, so definitely read more about it here:\n"
201+ ++ Help.hintLink "infinite-alias"
202+ ]
203+ )
176204
177205 Import name importError ->
178206 let moduleName = ModuleName.toString name
@@ -202,35 +230,34 @@ toReport localizer err =
202230 let
203231 boundPort =
204232 if isInbound then "inbound port" else "outbound port"
205−
206− preHint =
207− "Trying to send " ++ maybe "an unsupported type" id maybeMessage
208− ++ " through " ++ boundPort ++ " `" ++ name ++ "`"
209−
210− postHint =
211− "The specific unsupported type is:\n"
212− ++ "\n"
213− ++ error "TODO P.render (P.nest 4 (RenderType.doc localizer localType))" ++ "\n"
214− ++ "\n"
215− ++ "The types of values that can flow through " ++ boundPort ++ "s include:\n"
216− ++ "Ints, Floats, Bools, Strings, Maybes, Lists, Arrays, Tuples,\n"
217− ++ "Json.Values, and concrete records."
218233 in
219− Report.simple "PORT ERROR" preHint postHint
234+ Report.report
235+ "PORT ERROR"
236+ Nothing
237+ ("Trying to send " ++ maybe "an unsupported type" id maybeMessage
238+ ++ " through " ++ boundPort ++ " `" ++ name ++ "`"
239+ )
240+ ( Help.stack
241+ [ text "The specific unsupported type is:"
242+ , indent 4 (RenderType.toDoc localizer localType)
243+ , text ("The types of values that can flow through " ++ boundPort ++ "s include:")
244+ , indent 4 $ Help.reflowParagraph $
245+ "Ints, Floats, Bools, Strings, Maybes, Lists, Arrays,\
246+ \ Tuples, Json.Values, and concrete records."
247+ ]
248+ )
220249
221250
222251argMismatchReport :: String -> Var.Canonical -> Int -> Int -> Report.Report
223252argMismatchReport kind var expected actual =
224− let
225− preHint =
226− kind ++ " " ++ Var.toString var ++ " has too "
253+ Report.report
254+ "ARGUMENT"
255+ Nothing
256+ ( kind ++ " " ++ Var.toString var ++ " has too "
227257 ++ (if actual < expected then "few" else "many")
228258 ++ " arguments."
229−
230− postHint =
231− "Expecting " ++ show expected ++ ", but got " ++ show actual ++ "."
232− in
233− Report.simple "ARGUMENT" preHint postHint
259+ )
260+ (text ("Expecting " ++ show expected ++ ", but got " ++ show actual ++ "."))
234261
235262
236263extractSuggestions :: Error -> Maybe [String]
@@ -261,3 +288,23 @@ extractSuggestions err =
261288
262289 Port _ ->
263290 Nothing
291+
292+
293+unsafePromote :: Type.Raw -> Type.Canonical
294+unsafePromote (A.A _ rawType) =
295+ case rawType of
296+ Type.RLambda arg result ->
297+ Type.Lambda (unsafePromote arg) (unsafePromote result)
298+
299+ Type.RVar x ->
300+ Type.Var x
301+
302+ Type.RType (Var.Raw name) ->
303+ Type.Type (Var.local name)
304+
305+ Type.RApp func args ->
306+ Type.App (unsafePromote func) (map unsafePromote args)
307+
308+ Type.RRecord fields ext ->
309+ Type.Record (map (second unsafePromote) fields) (fmap unsafePromote ext)
310+
src/Reporting/Error/Docs.hs+32−19
@@ -1,10 +1,13 @@
11{-# OPTIONS_GHC -Wall #-}
22module Reporting.Error.Docs where
33
4+import Text.PrettyPrint.ANSI.Leijen (text)
5+
46import qualified Reporting.Error.Helpers as Help
57import qualified Reporting.Report as Report
68
79
10+
811data Error
912 = NoDocs
1013 | OnlyInDocs String [String]
@@ -13,49 +16,59 @@ data Error
1316 | NoType String
1417
1518
19+
1620-- TO REPORT
1721
22+
1823toReport :: Error -> Report.Report
1924toReport err =
2025 case err of
2126 NoDocs ->
22− Report.simple "DOCUMENTATION ERROR"
27+ Report.report
28+ "DOCUMENTATION ERROR"
29+ Nothing
2330 ( "You must have a documentation comment between the module declaration and the\n"
2431 ++ "imports."
2532 )
26− "Learn how at <http://package.elm-lang.org/help/documentation-format>"
33+ ( text "Learn more at <http://package.elm-lang.org/help/documentation-format>"
34+ )
2735
2836 OnlyInDocs name suggestions ->
29− Report.simple "DOCUMENTATION ERROR"
37+ Report.report
38+ "DOCUMENTATION ERROR"
39+ Nothing
3040 ("Your module documentation includes `" ++ name ++ "` which is not exported.")
31− ("Is it misspelled? Should it be exported? " ++ Help.maybeYouWant suggestions)
41+ ( text $
42+ "Is it misspelled? Should it be exported? "
43+ ++ Help.maybeYouWant suggestions
44+ )
3245
3346 OnlyInExports names ->
34− Report.simple
47+ Report.report
3548 "DOCUMENTATION ERROR"
49+ Nothing
3650 ( "The following exports do not appear in your module documentation:\n"
3751 ++ concatMap ("\n " ++) names
3852 )
39− ( "All exports must be listed in the module documentation after a @docs keyword.\n"
40− ++ "Learn how at <http://package.elm-lang.org/help/documentation-format>"
53+ ( text $
54+ "All exports must be listed in the module documentation after a @docs keyword.\n"
55+ ++ "Learn more at <http://package.elm-lang.org/help/documentation-format>"
4156 )
4257
4358 NoComment name ->
44− Report.simple "DOCUMENTATION ERROR"
59+ Report.report
60+ "DOCUMENTATION ERROR"
61+ Nothing
4562 ("The value `" ++ name ++ "` does not have a documentation comment.")
46− ( "Documentation comments start with {-| and end with -}. They should provide a\n"
47− ++ "clear description of how they work, and ideally a small code example. This is\n"
48− ++ "extremely valuable for users checking out your package!\n\n"
49− ++ "If you think the docs are clearer without any words, you can use an empty\n"
50− ++ "comment {-|-} which should be used sparingly. Maybe you have a section of 20\n"
51− ++ "values all with the exact same type. The docs may read better if they are all\n"
52− ++ "described in one place.\n\n"
53− ++ "Learn more at <http://package.elm-lang.org/help/documentation-format>"
63+ ( text "Learn more at <http://package.elm-lang.org/help/documentation-format>"
5464 )
5565
5666 NoType name ->
57− Report.simple "MISSING ANNOTATION"
67+ Report.report
68+ "MISSING ANNOTATION"
69+ Nothing
5870 ("The value `" ++ name ++ "` does not have a type annotation.")
59− ( "Adding type annotations is best practice and it gives you a chance to name\n"
60− ++ "types and type variables so they are as easy as possible to understand!"
71+ ( text $
72+ "Adding type annotations is best practice and it gives you a chance to name\n"
73+ ++ "types and type variables so they are as easy as possible to understand!"
6174 )
src/Reporting/Error/Helpers.hs+101−2
@@ -1,15 +1,114 @@
11{-# OPTIONS_GHC -Wall #-}
2−module Reporting.Error.Helpers where
2+module Reporting.Error.Helpers
3+ ( (|>)
4+ , functionName, hintLink, stack, reflowParagraph
5+ , commaSep, capitalize, ordinalize
6+ , nearbyNames, distance, maybeYouWant
7+ )
8+ where
39
410import Data.Function (on)
11+import qualified Data.Char as Char
512import qualified Data.List as List
613import qualified Text.EditDistance as Dist
14+import Text.PrettyPrint.ANSI.Leijen (Doc, (<>), fillSep, hardline, text)
15+
16+import qualified AST.Helpers as Help
17+import qualified Elm.Compiler.Version as Compiler
18+import qualified Elm.Package as Pkg
19+
20+
21+
22+-- PIPES
23+
24+
25+(|>) :: a -> (a -> b) -> b
26+(|>) x f =
27+ f x
28+
29+
30+infixl 0 |>
31+
32+
33+
34+-- DOC HELPERS
35+
36+
37+functionName :: String -> String
38+functionName opName =
39+ if Help.isOp opName then
40+ "(" ++ opName ++ ")"
41+
42+ else
43+ "`" ++ opName ++ "`"
44+
45+
46+hintLink :: String -> String
47+hintLink fileName =
48+ "<https://github.com/elm-lang/elm-compiler/blob/"
49+ ++ Pkg.versionToString Compiler.version
50+ ++ "/hints/" ++ fileName ++ ".md>"
51+
52+
53+stack :: [Doc] -> Doc
54+stack list =
55+ case list of
56+ [] ->
57+ error "function `stack` requires a non-empty list of docs"
58+
59+ doc : docs ->
60+ List.foldl' (\a b -> a <> hardline <> hardline <> b) doc docs
61+
62+
63+reflowParagraph :: String -> Doc
64+reflowParagraph paragraph =
65+ fillSep (map text (words paragraph))
66+
67+
68+commaSep :: [String] -> String
69+commaSep tokens =
70+ case tokens of
71+ [token] ->
72+ " " ++ token
73+
74+ [token1,token2] ->
75+ " " ++ token1 ++ " and " ++ token2
76+
77+ _ ->
78+ " " ++ List.intercalate ", " (init tokens) ++ ", and " ++ last tokens
79+
80+
81+capitalize :: String -> String
82+capitalize string =
83+ case string of
84+ [] -> []
85+ c : cs ->
86+ Char.toUpper c : cs
87+
88+
89+ordinalize :: Int -> String
90+ordinalize number =
91+ let
92+ remainder10 =
93+ number `mod` 10
94+
95+ remainder100 =
96+ number `mod` 100
97+
98+ ending
99+ | remainder100 `elem` [11..13] = "th"
100+ | remainder10 == 1 = "st"
101+ | remainder10 == 2 = "nd"
102+ | remainder10 == 3 = "rd"
103+ | otherwise = "th"
104+ in
105+ show number ++ ending
7106
8−import Elm.Utils ((|>))
9107
10108
11109-- NEARBY NAMES
12110
111+
13112nearbyNames :: (a -> String) -> a -> [a] -> [a]
14113nearbyNames format name names =
15114 let editDistance =
src/Reporting/Error/Pattern.hs+42−85
@@ -1,13 +1,15 @@
11{-# OPTIONS_GHC -Wall #-}
22module Reporting.Error.Pattern where
33
4−import qualified AST.Module.Name as ModuleName
5−import qualified AST.Variable as Var
4+import Text.PrettyPrint.ANSI.Leijen (text)
5+
66import qualified Nitpick.Pattern as Pattern
7+import qualified Reporting.Error.Helpers as Help
78import qualified Reporting.Render.Type as RenderType
89import qualified Reporting.Report as Report
910
1011
12+
1113data Error
1214 = Incomplete Origin [Pattern.Pattern]
1315 | Redundant
@@ -19,113 +21,68 @@ data Origin
1921 | Case
2022
2123
24+
2225-- TO REPORT
2326
27+
2428toReport :: RenderType.Localizer -> Error -> Report.Report
2529toReport _localizer err =
2630 case err of
2731 Incomplete Arg unhandled ->
28− Report.simple
32+ Report.report
2933 "PARTIAL PATTERN"
30− "This argument does not cover all possible inputs."
31− ( "Any of these patterns can cause a crash:\n"
32− ++ viewPatternList unhandled
33− ++ "\n\n"
34− ++ partialExplanation unhandled
35− )
34+ Nothing
35+ "This pattern does not cover all possible inputs."
36+ (text (unhandledError unhandled toCaseMessage))
3637
3738 Incomplete LetBound unhandled ->
38− Report.simple
39+ Report.report
3940 "PARTIAL PATTERN"
41+ Nothing
4042 "The pattern used here does not cover all possible values."
41− ( "Any of these patterns can cause a crash:\n"
42− ++ viewPatternList unhandled
43− ++ "\n\n"
44− ++ partialExplanation unhandled
45− )
43+ (text (unhandledError unhandled toCaseMessage))
4644
4745 Incomplete Case unhandled ->
48− Report.simple
49− "INCOMPLETE PATTERN MATCH"
46+ Report.report
47+ "MISSING PATTERNS"
48+ Nothing
5049 "This `case` does not have branches for all possibilities."
51− ( "Any of these patterns can cause a crash:\n"
52− ++ viewPatternList unhandled
53− ++ "\n\n"
54− ++ incompleteExplanation
55− )
50+ (text (unhandledError unhandled newTagsMessage))
5651
5752 Redundant ->
58− Report.simple
53+ Report.report
5954 "REDUNDANT PATTERN"
60− "The following pattern is redundant."
61− "Any value with this shape will be handled by a previous pattern."
55+ Nothing
56+ "The following pattern is redundant. Remove it."
57+ (text "Any value with this shape will be handled by a previous pattern.")
6258
6359
64−viewPatternList :: [Pattern.Pattern] -> String
65−viewPatternList unhandledPatterns =
60+unhandledError :: [Pattern.Pattern] -> String -> String
61+unhandledError unhandledPatterns relevantMessage =
6662 let
67− (showPatterns, rest) =
63+ (visiblePatterns, rest) =
6864 splitAt 4 unhandledPatterns
69− in
70− concatMap ((++) "\n ") $
71− map (Pattern.toString False) showPatterns
72− ++ if null rest then [] else ["..."]
73−
74−
75−partialExplanation :: [Pattern.Pattern] -> String
76−partialExplanation unhandledPatterns =
77−
78− "You probably want to switch to a `case` expression to handle all possible\n"
79− ++ "patterns in a simple way. If you think some cases are impossible, you can use\n"
80− ++ "`Debug.crash` to make that thinking explicit in your code.\n"
81− ++ "<http://package.elm-lang.org/packages/elm-lang/core/latest/Debug#crash>"
82− ++
83−
84− if isNothing unhandledPatterns then
85−
86− "\n\nSometimes you *know* a value will never be Nothing, though such cases are very\n"
87− ++ "rare. If you are certain you are in that case and that there is no better way\n"
88− ++ "to do things, I'd recommend creating a function `unsafe` that makes the danger\n"
89− ++ "explicit in the code. <https://github.com/elm-lang/core/issues/215>"
90−
91− else if isEmptyList unhandledPatterns then
92−
93− "\n\nSometimes you *know* a list cannot be empty, though such cases are relatively\n"
94− ++ "rare. If you are certain you are in that case and that there is no better\n"
95− ++ "way to do things, I'd recommend creating a function like `unsafe` that makes\n"
96− ++ "the danger explicit in the code. <https://github.com/elm-lang/core/issues/215>"
97−
98− else
99−
100− ""
101−
102−
103−isNothing :: [Pattern.Pattern] -> Bool
104−isNothing unhandledPatterns =
105− case unhandledPatterns of
106− [Pattern.Data (Var.Canonical (Var.Module moduleName) "Nothing") []] ->
107− moduleName == ModuleName.inCore ["Maybe"]
108−
109− _ ->
110− False
11165
66+ patternList =
67+ map (Pattern.toString False) visiblePatterns
68+ ++ if null rest then [] else ["..."]
69+ in
70+ "You need to account for the following values:\n"
71+ ++ concatMap ("\n " ++) patternList ++ "\n"
72+ ++ "\n"
73+ ++ relevantMessage ++ "\n"
74+ ++ "\n"
75+ ++ "If you are seeing this error for the first time, check out these hints:\n"
76+ ++ Help.hintLink "missing-patterns" ++ "\n"
77+ ++ "The recommendations about `Debug.crash` and wildcard patterns are important!"
11278
113−isEmptyList :: [Pattern.Pattern] -> Bool
114−isEmptyList unhandledPatterns =
115− case unhandledPatterns of
116− [Pattern.Data (Var.Canonical Var.BuiltIn "[]") []] ->
117− True
11879
119− _ ->
120− False
80+toCaseMessage :: String
81+toCaseMessage =
82+ "Switch to a `case` expression to handle all possible patterns."
12183
12284
123−incompleteExplanation :: String
124−incompleteExplanation =
125− "Did you add a new tag to a union type? Add how it should be handled here and\n"
126− ++ "you will be all set!\n"
127− ++ "\n"
128− ++ "If you want to go quick and implement this later, you may want to look into\n"
129− ++ "using `Debug.crash` which has some cool error reporting abilities.\n"
130− ++ "<http://package.elm-lang.org/packages/elm-lang/core/latest/Debug#crash>"
85+newTagsMessage :: String
86+newTagsMessage =
87+ "Did you add a new tag to a union type? Add a branch for it here!"
13188
src/Reporting/Error/Syntax.hs+125−83
@@ -4,9 +4,9 @@ module Reporting.Error.Syntax where
44import qualified Data.List as List
55import qualified Data.Set as Set
66import qualified Text.Parsec.Error as Parsec
7+import Text.PrettyPrint.ANSI.Leijen (dullyellow, hsep, text)
78
8−import qualified AST.Helpers as Help
9−import qualified AST.Type as Type
9+import qualified Reporting.Error.Helpers as Help
1010import qualified Reporting.Render.Type as RenderType
1111import qualified Reporting.Report as Report
1212
@@ -23,160 +23,189 @@ data Error
2323 | DuplicateValueDeclaration String
2424 | DuplicateTypeDeclaration String
2525 | DuplicateDefinition String
26− | UnboundTypeVarsInAlias String [String] String [String] Type.Raw
27− | UnboundTypeVarsInUnion String [String] String [String] [(String, [Type.Raw])]
26+ | UnboundTypeVarsInAlias String [String] String [String]
27+ | UnboundTypeVarsInUnion String [String] String [String]
2828
2929
3030-- TO REPORT
3131
3232toReport :: RenderType.Localizer -> Error -> Report.Report
33−toReport localizer err =
33+toReport _localizer err =
3434 case err of
3535 Parse messages ->
3636 parseErrorReport messages
3737
3838 BadFunctionName arity ->
39− Report.simple
39+ Report.report
4040 "BAD FUNCTION DEFINITION"
41+ Nothing
4142 "Complex patterns cannot be used as function names."
42− ( "This function definition has " ++ show arity ++ " arguments, but instead of a normal name like\n"
43− ++ "`add` or `reverse` it has a pattern. There is no way to \"deconstruct\" a\n"
44− ++ "function with pattern matching, so this needs to be changed to a normal name."
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."
4548 )
4649
4750 BadPattern name ->
48− Report.simple
51+ Report.report
4952 "BAD PATTERN"
53+ Nothing
5054 ("The free variable `" ++ name ++ "` appears more than once in this pattern.")
51− ( "Rename the variables so there are no duplicates.\n\n"
52− ++ "In Elm, pattern matching works by binding these free variables to subsections\n"
53− ++ "of the matching value. It does not make sense to have the same name for two\n"
54− ++ "different subsections though! When you say `" ++ name ++ "` in some code, there\n"
55− ++ "is no way for me to know which subsection you mean!"
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+ ]
5663 )
5764
5865 InfixDuplicate opName ->
59− Report.simple
66+ Report.report
6067 "INFIX OVERLAP"
61− ("The infix declarations for " ++ operator ++ " must be removed.")
62− ("The precedence and associativity can only be set in one place, and\n"
63− ++ "this information has already been set somewhere else."
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."
6474 )
65− where
66− operator =
67− if Help.isOp opName
68− then "(" ++ opName ++ ")"
69− else "`" ++ opName ++ "`"
7075
7176 TypeWithoutDefinition valueName ->
72− Report.simple
77+ Report.report
7378 "MISSING DEFINITION"
79+ Nothing
7480 ("There is a type annotation for `" ++ valueName ++ "` but there"
7581 ++ " is no corresponding definition!"
7682 )
77− ("Directly below the type annotation, put a definition like:\n\n"
78− ++ " " ++ valueName ++ " = 42"
83+ ( text $
84+ "Directly below the type annotation, put a definition like:\n\n"
85+ ++ " " ++ valueName ++ " = 42"
7986 )
8087
8188 PortWithoutAnnotation portName ->
82− Report.simple
89+ Report.report
8390 "PORT ERROR"
91+ Nothing
8492 ("Port `" ++ portName ++ "` does not have a type annotation!")
85− ("Directly above the port definition, I need something like this:\n\n"
86− ++ " port " ++ portName ++ " : Signal Int"
93+ ( text $
94+ "Directly above the port definition, I need something like this:\n\n"
95+ ++ " port " ++ portName ++ " : Signal Int"
8796 )
8897
8998
9099 UnexpectedPort ->
91− Report.simple
100+ Report.report
92101 "PORT ERROR"
102+ Nothing
93103 "This module has ports, but ports can only appear in the main module."
94− ( "Ports in library code would create hidden dependencies where importing a\n"
95− ++ "module could bring in constraints not captured in the public API. Furthermore,\n"
96− ++ "if the module is imported twice, do we send values out the port twice?"
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?"
97108 )
98109
99110 DuplicateFieldName name ->
100− Report.simple
111+ Report.report
101112 "DUPLICATE FIELD"
113+ Nothing
102114 ("This record has more than one field named `" ++ name ++ "`.")
103− "There can only be one. Do some renaming to make sure the names are distinct!"
115+ (text "There can only be one. Do some renaming to make sure the names are distinct!")
104116
105117 DuplicateValueDeclaration name ->
106− Report.simple
118+ Report.report
107119 "DUPLICATE DEFINITION"
108− ("Naming multiple top-level values `" ++ name ++ "` makes things\n"
120+ Nothing
121+ ( "Naming multiple top-level values `" ++ name ++ "` makes things\n"
109122 ++ "ambiguous. When you say `" ++ name ++ "` which one do you want?"
110123 )
111− ("Find all the top-level values named `" ++ name ++ "` and\n"
112− ++ "do some renaming. Make sure the names are distinct!"
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!"
113127 )
114128
115129 DuplicateTypeDeclaration name ->
116− Report.simple
130+ Report.report
117131 "DUPLICATE DEFINITION"
118− ("Naming multiple types `" ++ name ++ "` makes things ambiguous\n"
119− ++ "When you say `" ++ name ++ "` which one do you want?"
132+ Nothing
133+ ( "There are multiple types named `" ++ name ++ "` in this module."
120134 )
121− ("Find all the types named `" ++ name ++ "` and\n"
122− ++ "do some renaming. Make sure the names are distinct!"
135+ ( Help.reflowParagraph $
136+ "Search through this module, find all the types named `" ++ name
137+ ++ "`, and give each of them a unique name."
123138 )
124139
125140 DuplicateDefinition name ->
126− Report.simple
141+ Report.report
127142 "DUPLICATE DEFINITION"
128− ("Naming multiple values `" ++ name ++ "` in a single let-expression makes\n"
129− ++ "things ambiguous. When you say `" ++ name ++ "` which one do you want?"
143+ Nothing
144+ ("There are multiple values named `" ++ name ++ "` in this let-expression."
130145 )
131− ("Find all the values named `" ++ name ++ "` in this let-expression and\n"
132− ++ "do some renaming. Make sure the names are distinct!"
146+ ( Help.reflowParagraph $
147+ "Search through this let-expression, find all the values named `" ++ name
148+ ++ "`, and give each of them a unique name."
133149 )
134150
135− UnboundTypeVarsInAlias typeName givenVars tvar tvars tipe ->
136− unboundTypeVars typeName tvar tvars $
137− RenderType.aliasDecl localizer typeName (givenVars ++ tvar : tvars) tipe
151+ UnboundTypeVarsInAlias typeName givenVars tvar tvars ->
152+ unboundTypeVars "type alias" typeName givenVars tvar tvars
138153
139− UnboundTypeVarsInUnion typeName givenVars tvar tvars ctors ->
140− unboundTypeVars typeName tvar tvars $
141− RenderType.typeDecl localizer typeName (givenVars ++ tvar : tvars) ctors
154+ UnboundTypeVarsInUnion typeName givenVars tvar tvars ->
155+ unboundTypeVars "type" typeName givenVars tvar tvars
142156
143157
144−unboundTypeVars :: String -> String -> [String] -> String -> Report.Report
145−unboundTypeVars typeName tvar tvars revisedDeclaration =
146− Report.simple
147− "UNBOUND TYPE VARS"
148− ( "Not all type variables in `" ++ typeName ++ "` are listed, making sneaky\n"
149− ++ "type errors possible. Unbound type variables include: "
150− ++ List.intercalate ", " (tvar:tvars)
158+
159+unboundTypeVars :: String -> String -> [String] -> String -> [String] -> Report.Report
160+unboundTypeVars 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)
151166 )
152− ( "You probably want this definition instead:\n"
153− ++ concatMap ("\n "++) (lines revisedDeclaration) ++ "\n\n"
154− ++ "Here's why. Imagine one `" ++ typeName ++ "` where `" ++ tvar ++ "` is an Int and\n"
155− ++ "another where it is a Bool. When we explicitly list the type variables, type\n"
156− ++ "checker can see that they are actually different types."
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+ ]
157178 )
158179
159180
181+
160182-- TAGGING PARSE ERRORS
161183
184+
162185newline :: String
163186newline = "NEWLINE"
164187
188+
165189freshLine :: String
166190freshLine = "FRESH_LINE"
167191
192+
168193whitespace :: String
169194whitespace = "WHITESPACE"
170195
196+
171197keyword :: String -> String
172198keyword kwd =
173199 "KEYWORD=" ++ kwd
174200
201+
175202unkeyword :: String -> Maybe String
176203unkeyword message =
177− if List.isPrefixOf "KEYWORD=" message
178− then Just (drop (length "KEYWORD=") message)
179− else Nothing
204+ if List.isPrefixOf "KEYWORD=" message then
205+ Just (drop (length "KEYWORD=") message)
206+
207+ else
208+ Nothing
180209
181210
182211-- REPORTING PARSE ERRORS
@@ -195,9 +224,10 @@ parseErrorReport messages =
195224 Parsec.Expect msg ->
196225 let
197226 msg' =
198− if msg `elem` [whitespace, newline, freshLine]
199− then "whitespace"
200− else msg
227+ if msg `elem` [whitespace, newline, freshLine] then
228+ "whitespace"
229+ else
230+ msg
201231 in
202232 hint { _expected = Set.insert msg' (_expected hint) }
203233
@@ -207,26 +237,38 @@ parseErrorReport messages =
207237 (ParseHint msgs expects) =
208238 foldr addMsg emptyHint messages
209239
210− preHint =
240+ (preHint, maybePost) =
211241 case msgs of
212242 [msg] ->
213243 case unkeyword msg of
214244 Just kwd ->
215− "It looks like the keyword `" ++ kwd ++ "` is being used as a variable.\n"
216− ++ "Try renaming it to something else."
245+ ( "It looks like the keyword `" ++ kwd ++ "` is being used as a variable."
246+ , Just "Rename it to something else."
247+ )
248+
217249 Nothing ->
218− msg
250+ ( msg
251+ , Nothing
252+ )
219253
220− _ -> "I ran into something unexpected when parsing your code!"
254+ _ ->
255+ ( "I ran into something unexpected when parsing your code!"
256+ , Nothing
257+ )
221258
222259 postHint =
223− if Set.null expects
224− then ""
225− else
260+ if Set.null expects then
261+ "Maybe <http://elm-lang.org/docs/syntax> can help you figure it out."
262+
263+ else
226264 "I am looking for one of the following things:\n"
227265 ++ concatMap ("\n "++) (Set.toList expects)
228266 in
229− Report.simple "SYNTAX PROBLEM" preHint postHint
267+ Report.report
268+ "SYNTAX PROBLEM"
269+ Nothing
270+ preHint
271+ (text (maybe postHint id maybePost))
230272
231273
232274data ParseHint = ParseHint
src/Reporting/Error/Type.hs+232−245
@@ -1,10 +1,9 @@
11{-# OPTIONS_GHC -Wall #-}
22module Reporting.Error.Type where
33
4−import qualified Data.Char as Char
5−import qualified Data.List as List
4+import qualified Data.Maybe as Maybe
5+import Text.PrettyPrint.ANSI.Leijen (Doc, (<+>), equals, indent, text)
66
7−import qualified AST.Helpers as AstHelp
87import qualified AST.Type as Type
98import qualified AST.Variable as Var
109import qualified Reporting.Error.Helpers as Help
@@ -85,62 +84,52 @@ toReport localizer err =
8584 Mismatch info ->
8685 mismatchToReport localizer info
8786
88− InfiniteType name var tipe ->
89− let
90− prettyVar =
91− error "TODO P.pretty localizer False var"
87+ InfiniteType name overallType infiniteType ->
88+ infiniteTypeToReport localizer name overallType infiniteType
9289
93− prettyType =
94− error "TODO P.pretty localizer False tipe"
95− in
96− Report.simple "INFINITE TYPE"
97− ( "I am inferring weird self-referential type for `" ++ name ++ "`"
98− )
99− ( "TODO - do a better job\n\n"
100− ++ "The bit of the type that is self-referential looks like this:\n\n"
101− ++ error "TODO P.render (P.nest 4 (prettyVar <+> P.equals <+> prettyType))"
102− ++ "\n\nThe cause is often that the usage of `" ++ name ++ "` is flipped around.\n\n"
103− ++ "Maybe you are inserting a data structure into an element? Maybe you are giving\n"
104− ++ "a function to an argument? Either way, something is probably backwards!\n\n"
105− ++ "Try breaking the code related to `" ++ name ++ "` into smaller pieces.\n"
106− ++ "Give each piece a name and try to write down its type."
90+ BadMain tipe ->
91+ Report.report
92+ "BAD MAIN TYPE"
93+ Nothing
94+ "The 'main' value has an unsupported type."
95+ ( Help.stack
96+ [ Help.reflowParagraph $
97+ "I need an Element, Html, (Signal Element), or (Signal Html) so I can render it\
98+ \ on screen, but you gave me:"
99+ , indent 4 (RenderType.toDoc localizer tipe)
100+ ]
107101 )
108102
109− BadMain tipe ->
110− Report.simple "BAD MAIN TYPE" "The 'main' value has an unsupported type." $
111− "I need an Element, Html, (Signal Element), or (Signal Html) so I can render it\n"
112− ++ "on screen, but you gave me:\n\n"
113− ++ RenderType.tipe localizer tipe
103+
104+
105+-- TYPE MISMATCHES
114106
115107
116108mismatchToReport :: RenderType.Localizer -> Mismatch -> Report.Report
117109mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
118110 let
119111 report =
120− Report.Report "TYPE MISMATCH"
112+ Report.report "TYPE MISMATCH"
113+
114+ reasonHints =
115+ Maybe.maybeToList (fmap reasonToString maybeReason)
121116
122− pretty tipe =
123− "\n\n"
124− ++ RenderType.tipe localizer tipe
125− ++ "\n\n"
117+ cmpHint leftWords rightWords extraHints =
118+ comparisonHint localizer leftType rightType leftWords rightWords (extraHints ++ reasonHints)
126119 in
127120 case hint of
128121 CaseBranch branchNumber region ->
129122 report
130123 (Just region)
131− ( "The " ++ ordinalize (branchNumber -1) ++ " and " ++ ordinalize branchNumber
132− ++ " branches of this `case` result in different types of values."
124+ ( "The " ++ ordinalPair branchNumber
125+ ++ " branches of this `case` produce different types of values."
133126 )
134− ( "The " ++ ordinalize (branchNumber -1) ++ " branch results in this type of value:\n"
135−
136− ++ pretty leftType
137−
138− ++ "And the " ++ ordinalize branchNumber ++ " branch results in this type:\n"
139−
140− ++ pretty rightType
141−
142− ++ "These branches should match so that no matter which one we take, we always get\n"
143− ++ "back the same type of value."
127+ ( cmpHint
128+ ("The " ++ Help.ordinalize (branchNumber -1) ++ " branch has this type:")
129+ ("But the " ++ Help.ordinalize branchNumber ++ " is:")
130+ [ "All branches in a `case` must have the same type. So no matter\
131+ \ which one we take, we always get back the same type of value."
132+ ]
144133 )
145134
146135 Case ->
@@ -155,72 +144,62 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
155144 IfCondition ->
156145 report
157146 Nothing
158− ( "This condition does not evaluate to a boolean value, True or False."
159− )
160− ( "Instead of Bool, it is resulting in:\n"
161−
162− ++ pretty rightType
163−
164− ++ "Note: Elm does not have \"truthiness\" such that ints and strings and lists\n"
165− ++ "are automatically converted to booleans. Do that conversion explicitly."
147+ "This condition does not evaluate to a boolean value, True or False."
148+ ( cmpHint
149+ "You have given me an condition with this type:"
150+ "But I need it to be:"
151+ [ "Elm does not have \"truthiness\" such that ints and strings and lists\
152+ \ are automatically converted to booleans. Do that conversion explicitly."
153+ ]
166154 )
167155
168156 IfBranches ->
169157 report
170158 Nothing
171− ( "The branches of this `if` result in different types of values."
172− )
173− ( "The `then` branch results in:\n"
174−
175− ++ pretty leftType
176−
177− ++ "And the `else` branch results in:\n"
178−
179− ++ pretty rightType
180−
181− ++ "These need to match so that no matter which one we take, we always get\n"
182− ++ "back the same type of value."
159+ "The branches of this `if` produce different types of values."
160+ ( cmpHint
161+ "The `then` branch has type:"
162+ "But the `else` branch is:"
163+ [ "These need to match so that no matter which branch we take, we\
164+ \ always get back the same type of value."
165+ ]
183166 )
184167
185168 MultiIfBranch branchNumber region ->
186169 report
187170 (Just region)
188− ( "The " ++ ordinalize branchNumber ++ " branch of this `if` results in an unexpected type of value."
171+ ( "The " ++ ordinalPair branchNumber
172+ ++ " branches of this `if` produce different types of values."
189173 )
190− ( "The " ++ ordinalize (branchNumber - 1) ++ " branch results in:\n"
191−
192− ++ pretty leftType
193−
194− ++ "And the "++ ordinalize branchNumber ++ " branch results in:\n"
195−
196− ++ pretty rightType
197−
198− ++ "All branches should match so that no matter which one we take, we always get\n"
199− ++ "back the same type of value."
174+ ( cmpHint
175+ ("The " ++ Help.ordinalize (branchNumber - 1) ++ " branch has this type:")
176+ ("But the "++ Help.ordinalize branchNumber ++ " is:")
177+ [ "All the branches of an `if` need to match so that no matter which\
178+ \ one we take, we get back the same type of value overall."
179+ ]
200180 )
201181
202182 If ->
203183 report
204184 Nothing
205− ( "All the branches of this if-expression are consistent, but the overall\n"
206− ++ "type does not match how it is used elsewhere."
207− )
208− ( "The `if` evaluates to something of type:\n"
209−
210− ++ pretty leftType
211−
212− ++ "Which is fine, but the surrounding context wants it to be:\n"
213−
214− ++ pretty rightType
215−
185+ "All the branches of this `if` are consistent, but the overall\
186+ \ type does not match how it is used elsewhere."
187+ ( cmpHint
188+ "The `if` evaluates to something of type:"
189+ "Which is fine, but the surrounding context wants it to be:"
190+ []
216191 )
217192
218193 ListElement elementNumber region ->
219194 report
220195 (Just region)
221− ( "The " ++ ordinalize elementNumber ++ " element of this list is an unexpected type of value."
222− )
223− ( "All elements should be the same type of value so that we can iterate over the\nlist without running into unexpected values."
196+ ("The " ++ ordinalPair elementNumber ++ " elements are different types of values.")
197+ ( cmpHint
198+ ("The " ++ Help.ordinalize (elementNumber - 1) ++ " element has this type:")
199+ ("But the "++ Help.ordinalize elementNumber ++ " is:")
200+ [ "All elements should be the same type of value so that we can\
201+ \ iterate through the list without running into unexpected values."
202+ ]
224203 )
225204
226205 List ->
@@ -229,56 +208,43 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
229208 ( "All the elements in this list are the same type, but the overall\n"
230209 ++ "type does not match how it is used elsewhere."
231210 )
232− ( "The list has type:\n"
233−
234− ++ pretty leftType
235−
236− ++ "Which is fine, but the surrounding context wants it to be:\n"
237−
238− ++ pretty rightType
211+ ( cmpHint
212+ "The list has type:"
213+ "Which is fine, but the surrounding context wants it to be:"
214+ []
239215 )
240216
241217 BinopLeft op region ->
242218 report
243219 (Just region)
244− ("The left argument of " ++ prettyOperator op ++ " is causing a type mismatch.")
245− ( prettyOperator op ++ " is expecting:"
246−
247− ++ pretty leftType
248−
249− ++ "But the value on the left has this type:"
250−
251− ++ pretty rightType
220+ ("The left argument of " ++ prettyName op ++ " is causing a type mismatch.")
221+ ( cmpHint
222+ (prettyName op ++ " is expecting the left argument to be a:")
223+ "But the left argument is:"
224+ []
252225 )
253226
254227 BinopRight op region ->
255228 report
256229 (Just region)
257− ( "The right argument of " ++ prettyOperator op ++ " is causing a type mismatch."
258− )
259− ( prettyOperator op ++ " is expecting:"
260−
261− ++ pretty leftType
262−
263− ++ "But the value on the right has this type:"
264−
265− ++ pretty rightType
230+ ("The right argument of " ++ prettyName op ++ " is causing a type mismatch.")
231+ ( cmpHint
232+ (prettyName op ++ " is expecting the right argument to be a:")
233+ "But the right argument is:"
234+ []
266235 )
267236
268237 Binop op ->
269238 report
270239 Nothing
271− ( "The two arguments to " ++ prettyOperator op ++ " are fine, but the overall type of this expression\n"
272− ++ "does not match how it is used elsewhere."
240+ ( "The two arguments to " ++ prettyName op ++
241+ " are fine, but the overall type of this expression\
242+ \ does not match how it is used elsewhere."
273243 )
274− ( "The result of this binary operation is:\n"
275−
276− ++ pretty leftType
277−
278− ++ "Which is fine, but the surrounding context wants it to be:\n"
279−
280− ++ pretty rightType
281−
244+ ( cmpHint
245+ "The result of this binary operation is:"
246+ "Which is fine, but the surrounding context wants it to be:"
247+ []
282248 )
283249
284250 Function maybeName ->
@@ -286,30 +252,24 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
286252 Nothing
287253 ( "The return type of " ++ funcName maybeName ++ " is being used in unexpected ways."
288254 )
289− ( "The function results in a value with type:\n"
290−
291− ++ pretty leftType
292−
293− ++ "Which is fine, but the surrounding context wants it to be:\n"
294−
295− ++ pretty rightType
255+ ( cmpHint
256+ "The function results in this type of value:"
257+ "Which is fine, but the surrounding context wants it to be:"
258+ []
296259 )
297260
298261 UnexpectedArg maybeName index region ->
299262 report
300263 (Just region)
301− ( "The " ++ ordinalize index ++ " argument to " ++ funcName maybeName
264+ ( "The " ++ Help.ordinalize index ++ " argument to " ++ funcName maybeName
302265 ++ " is causing a mismatch."
303266 )
304− ( "The " ++ ordinalize index ++ " to " ++ funcName maybeName ++ " should be:"
305−
306− ++ pretty leftType
307−
308− ++ "But it is:"
309−
310− ++ pretty rightType
311−
312− ++ maybe "" reasonToString maybeReason
267+ ( cmpHint
268+ ( Help.capitalize (funcName maybeName) ++ " is expecting the "
269+ ++ Help.ordinalize index ++ " argument to be:"
270+ )
271+ "But it is:"
272+ []
313273 )
314274
315275 FunctionArity maybeName expected actual region ->
@@ -318,34 +278,29 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
318278 in
319279 report
320280 (Just region)
321− ( capitalize (funcName maybeName) ++ " is expecting " ++ show expected
281+ ( Help.capitalize (funcName maybeName) ++ " is expecting " ++ show expected
322282 ++ " argument" ++ s ++ ", but was given " ++ show actual ++ "."
323283 )
324− ( error "TODO are the types interesting here?"
325− )
284+ (text "Maybe you forgot some parentheses somewhere?")
326285
327286 BadTypeAnnotation name ->
328287 report
329288 Nothing
330− ( "The type annotation for `" ++ name ++ "` does not match its definition."
331− )
332− ( "Based on the actual definition, the type should be:\n"
333−
334− ++ pretty rightType
289+ ("The type annotation for " ++ Help.functionName name ++ " does not match its definition.")
290+ ( cmpHint
291+ "The type annotation is saying:"
292+ "But I am inferring that the definition has this type:"
293+ []
335294 )
336295
337296 Instance name ->
338297 report
339298 Nothing
340− ( "This usage of `" ++ name ++ "` is causing a type mismatch."
341− )
342− ( "Based on its definition, `" ++ name ++ "` has this type:"
343−
344− ++ pretty leftType
345−
346− ++ "But you are trying to use it as:"
347−
348− ++ pretty rightType
299+ (Help.functionName name ++ " is being used in an unexpected way.")
300+ ( cmpHint
301+ ("Based on its definition, " ++ Help.functionName name ++ " has this type:")
302+ "But you are trying to use it as:"
303+ []
349304 )
350305
351306 Literal name ->
@@ -353,9 +308,10 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
353308 Nothing
354309 ( "This " ++ name ++ " value is being used as if it is some other type of value."
355310 )
356− ( "You are trying to use this " ++ name ++ " as this type:"
357−
358− ++ pretty rightType
311+ ( cmpHint
312+ ("The " ++ name ++ " definitely has this type:")
313+ ("But it is being used as:")
314+ []
359315 )
360316
361317 Pattern patErr ->
@@ -364,71 +320,87 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
364320 case patErr of
365321 PVar name -> "variable `" ++ name ++ "`"
366322 PAlias name -> "alias `" ++ name ++ "`"
367− PData name -> "`" ++ name ++ "`"
323+ PData name -> "tag `" ++ name ++ "`"
368324 PRecord -> "a record"
369325 in
370326 report
371327 Nothing
372− ( "Problem with " ++ thing ++ " in this pattern match."
328+ ( Help.capitalize thing ++ " is causing problems in this pattern match."
373329 )
374− ( "This pattern matches things of type:"
375−
376− ++ pretty leftType
377−
378− ++ "But the values it will actually be trying to match are:"
379−
380− ++ pretty rightType
330+ ( cmpHint
331+ "This pattern matches things of type:"
332+ "But the values it will actually be trying to match are:"
333+ []
381334 )
382335
383336 Shader ->
384337 report
385338 Nothing
386− ( "There is some problem with this GLSL shader."
387− )
339+ "There is some problem with this GLSL shader."
388340 ( error "TODO generic error"
389341 )
390342
391343 Range ->
392344 report
393345 Nothing
394− ( "The low and high members of this list range are not the same type of value."
395− )
396− ( "The low end of the range has type:"
397−
398− ++ pretty leftType
399−
400− ++ "But the high end is:"
401−
402− ++ pretty rightType
346+ "The low and high members of this list range are not the same type of value."
347+ ( cmpHint
348+ "The low end of the range has type:"
349+ "But the high end is:"
350+ []
403351 )
404352
405353 Lambda ->
406354 report
407355 Nothing
408− ( "This anonymous function is being used in an unexpected way."
409− )
410− ( "The anonymous function has type:"
411−
412− ++ pretty leftType
413−
414− ++ "But you are trying to use it as:"
415−
416− ++ pretty rightType
356+ "This anonymous function is being used in an unexpected way."
357+ ( cmpHint
358+ "The anonymous function has type:"
359+ "But you are trying to use it as:"
360+ []
417361 )
418362
419363 Record ->
420364 report
421365 Nothing
422− ( "This record is being used in an unexpected way."
423− )
424− ( "The record has type:"
366+ "This record is being used in an unexpected way."
367+ ( cmpHint
368+ "The record has type:"
369+ "But you are trying to use it as:"
370+ []
371+ )
372+
373+
374+comparisonHint
375+ :: RenderType.Localizer
376+ -> Type.Canonical
377+ -> Type.Canonical
378+ -> String
379+ -> String
380+ -> [String]
381+ -> Doc
382+comparisonHint localizer leftType rightType leftWords rightWords finalHints =
383+ let
384+ (leftDoc, rightDoc) =
385+ RenderType.diffToDocs localizer RenderType.Elide leftType rightType
386+ in
387+ Help.stack $
388+ [ Help.reflowParagraph leftWords
389+ , indent 4 leftDoc
390+ , Help.reflowParagraph rightWords
391+ , indent 4 rightDoc
392+ ]
393+ ++
394+ map Help.reflowParagraph finalHints
425395
426− ++ pretty leftType
427396
428− ++ "But you are trying to use it as:"
397+ordinalPair :: Int -> String
398+ordinalPair number =
399+ Help.ordinalize (number -1 ) ++ " and " ++ Help.ordinalize number
400+
429401
430− ++ pretty rightType
431− )
402+
403+-- MISMTACH REASONS
432404
433405
434406reasonToString :: Reason -> String
@@ -445,12 +417,12 @@ reasonToString reason =
445417 [] ->
446418 "These records look pretty different."
447419
448− [(exp,act)] ->
449− "Looks like a misspelling between field " ++ exp ++ " and " ++ act ++ "."
420+ [(ex,ac)] ->
421+ "Looks like a misspelling between field " ++ ex ++ " and " ++ ac ++ "."
450422
451423 potentialTypos ->
452424 "There are some potential typos in the record field names.\n"
453− ++ concatMap (\(exp,act) -> "\n " ++ exp ++ " <=> " ++ act) potentialTypos
425+ ++ concatMap (\(ex,ac) -> "\n " ++ ex ++ " <=> " ++ ac) potentialTypos
454426
455427 NotRecord ->
456428 "A record cannot be merged with other types of values."
@@ -467,29 +439,15 @@ similarNames otherKeys key =
467439 map ((,) key) (filter (\key' -> Help.distance key key' == 1) otherKeys)
468440
469441
470−
471442genericMissingFieldsMessage :: String -> [String] -> String
472443genericMissingFieldsMessage latterOrFormer fields =
473− "A record in the " ++ latterOrFormer ++ " type is missing "
474− ++
475− case fields of
476− [key] ->
477− "field `" ++ key ++ "`"
478−
479− [key1,key2] ->
480− "fields " ++ key1 ++ " and " ++ key2
444+ "A record in the " ++ latterOrFormer ++ " type is missing field"
445+ ++ Help.commaSep fields
481446
482− keys ->
483− "fields " ++ List.intercalate ", " (init keys) ++ ", and " ++ last keys
484447
485−
486−prettyOperator :: Var.Canonical -> String
487−prettyOperator (Var.Canonical _ opName) =
488− if AstHelp.isOp opName then
489− "(" ++ opName ++ ")"
490−
491− else
492− "`" ++ opName ++ "`"
448+prettyName :: Var.Canonical -> String
449+prettyName (Var.Canonical _ opName) =
450+ Help.functionName opName
493451
494452
495453funcName :: Maybe Var.Canonical -> String
@@ -499,33 +457,62 @@ funcName maybeVar =
499457 "this function"
500458
501459 Just var ->
502− "function " ++ prettyOperator var
503−
504−
505−capitalize :: String -> String
506−capitalize string =
507− case string of
508− [] -> []
509− c : cs ->
510− Char.toUpper c : cs
511−
512−
513−ordinalize :: Int -> String
514−ordinalize number =
460+ "function " ++ prettyName var
461+
462+
463+
464+-- INFINITE TYPES
465+
466+
467+infiniteTypeToReport
468+ :: RenderType.Localizer
469+ -> String
470+ -> Type.Canonical
471+ -> Type.Canonical
472+ -> Report.Report
473+infiniteTypeToReport localizer name overallType infiniteType =
474+ Report.report
475+ "INFINITE TYPE"
476+ Nothing
477+ ( "I am inferring a weird self-referential type for " ++ Help.functionName name
478+ )
479+ ( Help.stack $
480+ explainInfiniteType localizer name overallType infiniteType
481+ ++
482+ [ text $
483+ "This can be very tricky to figure out, so definitely read the debugging hints:\n"
484+ ++ Help.hintLink "infinite-type"
485+ ]
486+ )
487+
488+
489+explainInfiniteType
490+ :: RenderType.Localizer
491+ -> String
492+ -> Type.Canonical
493+ -> Type.Canonical
494+ -> [Doc]
495+explainInfiniteType localizer name overallType infiniteType =
515496 let
516− remainder10 =
517− number `mod` 10
518−
519− remainder100 =
520− number `mod` 100
521−
522− ending
523− | remainder100 `elem` [11..13] = "th"
524− | remainder10 == 1 = "st"
525− | remainder10 == 2 = "nd"
526− | remainder10 == 3 = "rd"
527− | otherwise = "th"
497+ selfReference =
498+ text "?" <+> equals <+> RenderType.toDoc localizer infiniteType
528499 in
529− show number ++ ending
530−
531−
500+ case overallType of
501+ Type.Var "?" ->
502+ [ Help.reflowParagraph $
503+ "I cannot write down the actual type of " ++ Help.functionName name ++
504+ " because it is infinitely large (that's the problem!) so here it\
505+ \ is as an equation where `?` represents the type:"
506+ , indent 4 selfReference
507+ ]
508+
509+ _ ->
510+ [ Help.reflowParagraph $
511+ "I am inferring that " ++ Help.functionName name ++ " has this type:"
512+ , indent 4 (RenderType.toDoc localizer overallType)
513+ , Help.reflowParagraph $
514+ "The problem is in the `?` which seems to contain itself. I cannot show\
515+ \ you that type directly (because it is infinitely large!) so here it is\
516+ \ as an equation:"
517+ , indent 4 selfReference
518+ ]
src/Reporting/Render/Code.hsnew file+113
@@ -0,0 +1,113 @@
1+{-# OPTIONS_GHC -Wall #-}
2+module Reporting.Render.Code
3+ ( render
4+ )
5+ where
6+
7+import Control.Applicative ((<|>))
8+import Text.PrettyPrint.ANSI.Leijen (Doc, (<>), hardline, dullred, empty, text)
9+
10+import qualified Reporting.Region as R
11+
12+
13+(|>) :: a -> (a -> b) -> b
14+(|>) a f =
15+ f a
16+
17+
18+(<==>) :: Doc -> Doc -> Doc
19+(<==>) a b =
20+ a <> hardline <> b
21+
22+
23+render :: Maybe R.Region -> R.Region -> String -> Doc
24+render maybeSubRegion region@(R.Region start end) source =
25+ let
26+ (R.Position startLine startColumn) = start
27+ (R.Position endLine endColumn) = end
28+
29+ relevantLines =
30+ lines source
31+ |> drop (startLine - 1)
32+ |> take (endLine - startLine + 1)
33+ in
34+ case relevantLines of
35+ [] ->
36+ empty
37+
38+ [sourceLine] ->
39+ singleLineRegion startLine sourceLine $
40+ case maybeSubRegion of
41+ Nothing ->
42+ (0, startColumn, endColumn, length sourceLine)
43+
44+ Just (R.Region s e) ->
45+ (startColumn, R.column s, R.column e, endColumn)
46+
47+ firstLine : rest ->
48+ let
49+ filteredFirstLine =
50+ replicate (startColumn - 1) ' '
51+ ++ drop (startColumn - 1) firstLine
52+
53+ filteredLastLine =
54+ take (endColumn) (last rest)
55+
56+ focusedRelevantLines =
57+ filteredFirstLine : init rest ++ [filteredLastLine]
58+
59+ lineNumbersWidth =
60+ length (show endLine)
61+
62+ subregion =
63+ maybeSubRegion <|> Just region
64+
65+ numberedLines =
66+ zipWith
67+ (addLineNumber subregion lineNumbersWidth)
68+ [startLine .. endLine]
69+ focusedRelevantLines
70+ in
71+ foldr (<==>) empty numberedLines
72+
73+
74+addLineNumber :: Maybe R.Region -> Int -> Int -> String -> Doc
75+addLineNumber maybeSubRegion width n line =
76+ let
77+ number =
78+ if n < 0 then " " else show n
79+
80+ lineNumber =
81+ replicate (width - length number) ' ' ++ number ++ "│"
82+
83+ spacer (R.Region start end) =
84+ if R.line start <= n && n <= R.line end then
85+ dullred (text ">")
86+ else
87+ text " "
88+ in
89+ text lineNumber
90+ <> maybe (text " ") spacer maybeSubRegion
91+ <> text line
92+
93+
94+singleLineRegion :: Int -> String -> (Int, Int, Int, Int) -> Doc
95+singleLineRegion lineNum sourceLine (start, innerStart, innerEnd, end) =
96+ let
97+ width =
98+ length (show lineNum)
99+
100+ underline =
101+ text (replicate (innerStart + width + 1) ' ')
102+ <>
103+ dullred (text (replicate (max 1 (innerEnd - innerStart)) '^'))
104+
105+ trimmedSourceLine =
106+ sourceLine
107+ |> drop (start - 1)
108+ |> take (end - start + 1)
109+ |> (++) (replicate (start - 1) ' ')
110+ in
111+ addLineNumber Nothing width lineNum trimmedSourceLine
112+ <==>
113+ underline
src/Reporting/Render/Type.hs+47−31
@@ -1,7 +1,6 @@
11module Reporting.Render.Type
22 ( Localizer
33 , decl
4− , aliasDecl
54 , annotation
65 , toDoc
76 , diffToDocs
@@ -13,10 +12,11 @@ import Control.Arrow ((***), first)
1312import qualified Data.Map as Map
1413import qualified Data.Set as Set
1514import Text.PrettyPrint.ANSI.Leijen
16− ( Doc, (<+>), cat, colon, comma, dullyellow, equals, hang, hsep, indent
17− , lbrace, lparen, rbrace, rparen, sep, text, vcat
15+ ( Doc, (<>), (<+>), cat, colon, comma, dullyellow, equals, hang, hsep
16+ , indent, lbrace, lparen, parens, rbrace, rparen, sep, space, text, vcat
1817 )
1918
19+import qualified AST.Helpers as Help
2020import qualified AST.Type as Type
2121import qualified AST.Variable as Var
2222import Reporting.Error.Helpers as Help
@@ -26,6 +26,21 @@ import Reporting.Error.Helpers as Help
2626-- PUBLIC API FOR CREATING DOCS
2727
2828
29+toDoc :: Localizer -> Type.Canonical -> Doc
30+toDoc localizer tipe =
31+ docType localizer tipe
32+
33+
34+diffToDocs :: Localizer -> Style -> Type.Canonical -> Type.Canonical -> (Doc,Doc)
35+diffToDocs localizer style leftType rightType =
36+ case diff localizer style leftType rightType of
37+ Same doc ->
38+ (doc, doc)
39+
40+ Diff leftDoc rightDoc ->
41+ (leftDoc, rightDoc)
42+
43+
2944decl :: Localizer -> String -> [String] -> [(String, [Type.Canonical])] -> Doc
3045decl localizer name vars tags =
3146 let
@@ -40,31 +55,25 @@ decl localizer name vars tags =
4055 (map docTag tags)
4156
4257
43−aliasDecl :: Localizer -> String -> [String] -> Type.Canonical -> Doc
44−aliasDecl localizer name vars tipe =
45− hang 4 $ vcat $
46− [ hsep (map text ("type" : name : vars) ++ [equals])
47− , docType localizer tipe
48− ]
49−
50−
51−annotation =
52− error "TODO"
53−
54−
55−toDoc :: Localizer -> Type.Canonical -> Doc
56−toDoc localizer tipe =
57− docType localizer tipe
58−
59−
60−diffToDocs :: Localizer -> Style -> Type.Canonical -> Type.Canonical -> (Doc,Doc)
61−diffToDocs localizer style leftType rightType =
62− case diff localizer style leftType rightType of
63− Same doc ->
64− (doc, doc)
58+annotation :: Localizer -> String -> Type.Canonical -> Doc
59+annotation localizer name tipe =
60+ let
61+ docName =
62+ if Help.isOp name then parens (text name) else text name
63+ in
64+ case Type.collectLambdas tipe of
65+ parts@(_ : _ : _) ->
66+ hang 4 $ sep $
67+ docName
68+ : zipWith (<+>)
69+ ((colon <> space) : repeat (text "->"))
70+ (map (docType localizer) parts)
6571
66− Diff leftDoc rightDoc ->
67− (leftDoc, rightDoc)
72+ _ ->
73+ hang 4 $ sep $
74+ [ docName <+> colon
75+ , docType localizer tipe
76+ ]
6877
6978
7079
@@ -430,8 +439,13 @@ docType localizer tipe =
430439
431440
432441docLambda :: [Doc] -> Doc
433−docLambda (arg:rest) =
434− sep (arg : map (text "->" <+>) rest)
442+docLambda docs =
443+ case docs of
444+ [] ->
445+ error "cannot call docLambda with an empty list"
446+
447+ arg:rest ->
448+ sep (arg : map (text "->" <+>) rest)
435449
436450
437451
@@ -480,8 +494,10 @@ docRecord style fields maybeExt =
480494
481495 (_, Just ext) ->
482496 sep
483− [ lbrace <+> ext
484− , indent 4 (cat (zipWith (<+>) (text "|" : repeat comma) fieldDocs))
497+ [ hang 4 $ sep $
498+ [ lbrace <+> ext
499+ , cat (zipWith (<+>) (text "|" : repeat comma) fieldDocs)
500+ ]
485501 , rbrace
486502 ]
487503
src/Reporting/Report.hs+50−195
@@ -1,64 +1,44 @@
11{-# OPTIONS_GHC -Wall #-}
22{-# LANGUAGE OverloadedStrings #-}
33module Reporting.Report
4− ( Report(Report)
5− , simple
4+ ( Report
5+ , report
66 , toString
77 , toJson
8− , printError, printWarning
9− ) where
8+ , toHandle
9+ )
10+ where
1011
11−import Control.Applicative ((<|>))
12−import Control.Monad.Writer (Writer, execWriter, tell)
1312import Data.Aeson ((.=))
1413import qualified Data.Aeson.Types as Json
15−import System.Console.ANSI
16−import System.IO (hPutStr, stderr)
14+import System.IO (Handle)
15+import Text.PrettyPrint.ANSI.Leijen
16+ ( Doc, (<>), displayS, displayIO, dullcyan, fillSep, hardline, renderPretty, text
17+ )
1718
1819import qualified Reporting.Region as R
20+import qualified Reporting.Render.Code as Code
21+
22+
23+
24+-- BUILD REPORTS
1925
2026
2127data Report = Report
2228 { _title :: String
2329 , _highlight :: Maybe R.Region
24− , _preHint :: String
25− , _postHint :: String
30+ , _preHint :: Doc
31+ , _postHint :: Doc
2632 }
2733
2834
29−simple :: String -> String -> String -> Report
30−simple title pre post =
31− Report title Nothing pre post
32−
33−
34−toString :: String -> R.Region -> Report -> String -> String
35−toString location region report source =
36− execWriter (render plain location region report source)
37−
35+report :: String -> Maybe R.Region -> String -> Doc -> Report
36+report title highlight pre post =
37+ Report title highlight (fillSep (map text (words pre))) post
3838
39−printError :: String -> R.Region -> Report -> String -> IO ()
40−printError location region report source =
41− render (ansi Error) location region report source
4239
4340
44−printWarning :: String -> R.Region -> Report -> String -> IO ()
45−printWarning location region report source =
46− render (ansi Warning) location region report source
47−
48−
49−render
50− :: (Monad m)
51− => Renderer m
52− -> String
53− -> R.Region
54− -> Report
55− -> String
56− -> m ()
57−render renderer location region (Report title highlight pre post) source =
58− do messageBar renderer title location
59− normal renderer (pre ++ "\n\n")
60− grabRegion renderer highlight region source
61− normal renderer ("\n" ++ if null post then "\n" else post ++ "\n\n\n")
41+-- REPORT TO JSON
6242
6343
6444toJson :: [Json.Pair] -> Report -> (Maybe R.Region, [Json.Pair])
@@ -66,177 +46,52 @@ toJson extraFields (Report title subregion pre post) =
6646 let
6747 fields =
6848 [ "tag" .= title
69− , "overview" .= pre
70− , "details" .= post
49+ , "overview" .= displayS (renderPretty 1 80 pre) ""
50+ , "details" .= displayS (renderPretty 1 80 post) ""
7151 ]
7252 in
7353 (subregion, fields ++ extraFields)
7454
7555
76−-- RENDERING
77−
78−data Renderer m = Renderer
79− { normal :: String -> m ()
80− , header :: String -> m ()
81− , accent :: String -> m ()
82− }
83−
84−
85−plain :: Renderer (Writer String)
86−plain =
87− Renderer tell tell tell
8856
57+-- REPORT TO DOC
8958
90−data Type = Error | Warning
91−
92−
93−ansi :: Type -> Renderer IO
94−ansi tipe =
95− let
96− put =
97− hPutStr stderr
98−
99− put' intensity color string =
100− do hSetSGR stderr [SetColor Foreground intensity color]
101− put string
102− hSetSGR stderr [Reset]
103−
104− accentColor =
105− case tipe of
106− Error -> Red
107− Warning -> Yellow
108− in
109− Renderer
110− put
111− (put' Dull Cyan)
112− (put' Dull accentColor)
11359
60+toDoc :: String -> R.Region -> Report -> String -> Doc
61+toDoc location region (Report title highlight preHint postHint) source =
62+ messageBar title location
63+ <> hardline <> hardline <>
64+ preHint
65+ <> hardline <>
66+ Code.render highlight region source
67+ <> hardline <>
68+ postHint
69+ <> hardline
11470
11571
116−
117−-- REPORT HEADER
118−
119−messageBar :: Renderer m -> String -> String -> m ()
120−messageBar renderer tag location =
72+messageBar :: String -> String -> Doc
73+messageBar tag location =
12174 let
122− usedSpace = 4 + length tag + 1 + length location
75+ usedSpace =
76+ 4 + length tag + 1 + length location
12377 in
124− header renderer $
125− "-- " ++ tag ++ " "
126− ++ replicate (max 1 (80 - usedSpace)) '-'
127− ++ " " ++ location ++ "\n\n"
128−
129−
130−-- REGIONS
131−
132−grabRegion
133− :: (Monad m)
134− => Renderer m
135− -> Maybe R.Region
136− -> R.Region
137− -> String
138− -> m ()
139−grabRegion renderer maybeSubRegion region@(R.Region start end) source =
140− let
141− (R.Position startLine startColumn) = start
142− (R.Position endLine endColumn) = end
78+ dullcyan $ text $
79+ "-- " ++ tag ++ " " ++ replicate (max 1 (80 - usedSpace)) '-' ++ " " ++ location
14380
144− (|>) = flip ($)
14581
146− relevantLines =
147− lines source
148− |> drop (startLine - 1)
149− |> take (endLine - startLine + 1)
150− in
151− case relevantLines of
152− [] ->
153− normal renderer ""
154−
155− [sourceLine] ->
156− singleLineRegion renderer startLine sourceLine $
157− case maybeSubRegion of
158− Nothing ->
159− (0, startColumn, endColumn, length sourceLine)
160−
161− Just (R.Region s e) ->
162− (startColumn, R.column s, R.column e, endColumn)
163−
164− firstLine : rest ->
165− let
166− filteredFirstLine =
167− replicate (startColumn - 1) ' '
168− ++ drop (startColumn - 1) firstLine
169−
170− filteredLastLine =
171− take (endColumn) (last rest)
172−
173− focusedRelevantLines =
174− filteredFirstLine : init rest ++ [filteredLastLine]
175−
176− lineNumbersWidth =
177− length (show endLine)
178−
179− subregion =
180− maybeSubRegion <|> Just region
181−
182− numberedLines =
183− zipWith
184− (addLineNumber renderer subregion lineNumbersWidth)
185− [startLine .. endLine]
186− focusedRelevantLines
187− in
188− mapM_ (\line -> line >> normal renderer "\n") numberedLines
189−
190−
191−addLineNumber
192− :: (Monad m)
193− => Renderer m
194− -> Maybe R.Region
195− -> Int
196− -> Int
197− -> String
198− -> m ()
199−addLineNumber renderer maybeSubRegion width n line =
200− let
201− number =
202− if n < 0 then " " else show n
20382
204− lineNumber =
205− replicate (width - length number) ' ' ++ number ++ "│"
83+-- RENDER DOCS
20684
207− spacer (R.Region start end) =
208− if R.line start <= n && n <= R.line end
209− then accent renderer ">"
210− else normal renderer " "
211− in
212− do normal renderer lineNumber
213− maybe (normal renderer " ") spacer maybeSubRegion
214− normal renderer line
215−
216−
217−singleLineRegion
218− :: (Monad m)
219− => Renderer m
220− -> Int
221− -> String
222− -> (Int, Int, Int, Int)
223− -> m ()
224−singleLineRegion renderer lineNum sourceLine (start, innerStart, innerEnd, end) =
225− let
226− width =
227− length (show lineNum)
22885
229− underline =
230− replicate (innerStart + width + 1) ' '
231− ++ replicate (max 1 (innerEnd - innerStart)) '^'
86+toString :: String -> R.Region -> Report -> String -> String
87+toString location region rprt source =
88+ displayS
89+ (renderPretty 1 80 (toDoc location region rprt source))
90+ ""
23291
233− (|>) = flip ($)
23492
235− trimmedSourceLine =
236− sourceLine
237− |> drop (start - 1)
238− |> take (end - start + 1)
239− |> (++) (replicate (start - 1) ' ')
240− in
241− do addLineNumber renderer Nothing width lineNum trimmedSourceLine
242− accent renderer $ "\n" ++ underline
93+toHandle :: Handle -> String -> R.Region -> Report -> String -> IO ()
94+toHandle handle location region rprt source =
95+ displayIO
96+ handle
97+ (renderPretty 1 80 (toDoc location region rprt source))
src/Reporting/Warning.hs+16−15
@@ -4,54 +4,55 @@ module Reporting.Warning where
44
55import Data.Aeson ((.=))
66import qualified Data.Aeson as Json
7+import Text.PrettyPrint.ANSI.Leijen (text)
78
89import qualified AST.Module.Name as ModuleName
910import qualified AST.Type as Type
1011import qualified Reporting.Annotation as A
12+import qualified Reporting.Error.Helpers as Help
1113import qualified Reporting.Report as Report
1214import qualified Reporting.Render.Type as RenderType
1315
1416
17+
1518-- ALL POSSIBLE WARNINGS
1619
20+
1721data Warning
1822 = UnusedImport ModuleName.Raw
1923 | MissingTypeAnnotation String Type.Canonical
2024
2125
22−-- TO STRING
23−
24−toString :: RenderType.Localizer -> String -> String -> A.Located Warning -> String
25−toString localizer location source (A.A region warning) =
26− Report.toString location region (toReport localizer warning) source
2726
28−
29−print :: RenderType.Localizer -> String -> String -> A.Located Warning -> IO ()
30−print localizer location source (A.A region warning) =
31− Report.printWarning location region (toReport localizer warning) source
27+-- TO REPORT
3228
3329
3430toReport :: RenderType.Localizer -> Warning -> Report.Report
3531toReport localizer warning =
3632 case warning of
3733 UnusedImport moduleName ->
38− Report.simple
34+ Report.report
3935 "unused import"
36+ Nothing
4037 ("Module `" ++ ModuleName.toString moduleName ++ "` is unused.")
41− ""
38+ (text "Best to remove it. Don't save code quality for later!")
4239
4340 MissingTypeAnnotation name inferredType ->
44− Report.simple
41+ Report.report
4542 "missing type annotation"
46− ("Top-level value `" ++ name ++ "` does not have a type annotation.")
47− ( "The type annotation you want looks something like this:\n\n"
48− ++ RenderType.annotation name inferredType
43+ Nothing
44+ ("Top-level value " ++ Help.functionName name ++ " does not have a type annotation.")
45+ ( Help.stack
46+ [ text "I inferred the type annotation so you can copy it into your code:"
47+ , RenderType.annotation localizer name inferredType
48+ ]
4949 )
5050
5151
5252
5353-- TO JSON
5454
55+
5556toJson :: RenderType.Localizer -> FilePath -> A.Located Warning -> Json.Value
5657toJson localizer filePath (A.A region warning) =
5758 let
src/Type/Constrain/Literal.hs+2−2
@@ -14,8 +14,8 @@ constrain
1414 -> T.Type
1515 -> IO T.TypeConstraint
1616constrain env region literal tipe =
17− do tipe' <- litType
18− return (T.CEqual (Error.Literal name) region tipe tipe')
17+ do definiteType <- litType
18+ return (T.CEqual (Error.Literal name) region definiteType tipe)
1919 where
2020 prim name =
2121 return (Env.getType env name)
src/Validate.hs+2−2
@@ -403,7 +403,7 @@ checkDecl isRoot (A.A (region,_) decl) =
403403 (v:vs) = Set.toList (Set.fromList unbound)
404404 in
405405 Result.throw region
406− (Error.UnboundTypeVarsInUnion name boundVars v vs ctors)
406+ (Error.UnboundTypeVarsInUnion name boundVars v vs)
407407
408408 D.TypeAlias name boundVars tipe ->
409409 case findUnbound boundVars (freeVars tipe) of
@@ -415,7 +415,7 @@ checkDecl isRoot (A.A (region,_) decl) =
415415 (v:vs) = Set.toList (Set.fromList unbound)
416416 in
417417 Result.throw region
418− (Error.UnboundTypeVarsInAlias name boundVars v vs tipe)
418+ (Error.UnboundTypeVarsInAlias name boundVars v vs)
419419
420420 D.Port _ ->
421421 if isRoot