@@ -1,11 +1,13 @@
1{-# OPTIONS_GHC -Wall #-}
1{-# OPTIONS_GHC -Wall #-}
2module Reporting.Error.Type where
2module Reporting.Error.Type where
4+import Control.Arrow (first, second)
5+import qualified Data.List as List
4import qualified Data.Map as Map
6import qualified Data.Map as Map
5import qualified Data.Maybe as Maybe
7import qualified Data.Maybe as Maybe
6import Text.PrettyPrint.ANSI.Leijen
8import Text.PrettyPrint.ANSI.Leijen
7 ( Doc, (<>), (<+>), colon, dullyellow
9 ( Doc, (<>), (<+>), colon, dullyellow
8− , fillSep, hang, indent, text, underline, vcat
10+ , fillSep, indent, text, underline, vcat
11import qualified AST.Type as Type
13import qualified AST.Type as Type
@@ -36,9 +38,11 @@ data Mismatch = MismatchInfo
38data Reason
40data Reason
41+ = BadFields [(String, Maybe Reason)]
39− = MessyFields [String] [String]
42+ | MessyFields [String] [String] [String]
40 | IntFloat
43 | IntFloat
41 | TooLongComparableTuple Int
44 | TooLongComparableTuple Int
42 | BadVar (Maybe VarType) (Maybe VarType)
46 | BadVar (Maybe VarType) (Maybe VarType)
@@ -65,13 +69,13 @@ data Hint
65 | Function (Maybe Var.Canonical)
69 | Function (Maybe Var.Canonical)
66 | UnexpectedArg (Maybe Var.Canonical) Int Int Region.Region
70 | UnexpectedArg (Maybe Var.Canonical) Int Int Region.Region
67 | FunctionArity (Maybe Var.Canonical) Int Int Region.Region
71 | FunctionArity (Maybe Var.Canonical) Int Int Region.Region
68− | BadTypeAnnotation String
72+ | ReturnType String Int Int Region.Region
69 | Instance String
73 | Instance String
70 | Literal String
74 | Literal String
71 | Pattern Pattern
75 | Pattern Pattern
78+ | Access (Maybe String) String
76 -- effect manager problems
80 -- effect manager problems
77 | Manager String
81 | Manager String
@@ -142,11 +146,12 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
143 Report.report "TYPE MISMATCH"
147 Report.report "TYPE MISMATCH"
150+ Maybe.maybeToList (reasonToString =<< maybeReason)
145 cmpHint leftWords rightWords extraHints =
152 cmpHint leftWords rightWords extraHints =
146 comparisonHint localizer leftType rightType leftWords rightWords
153 comparisonHint localizer leftType rightType leftWords rightWords
147− ( Maybe.maybeToList (reasonToString =<< maybeReason)
148− ++ map toHint extraHints
154+ (typicalHints ++ map toHint extraHints)
151 case hint of
156 case hint of
152 CaseBranch branchNumber region ->
157 CaseBranch branchNumber region ->
@@ -227,12 +232,14 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
227 ListElement elementNumber region ->
232 ListElement elementNumber region ->
229 (Just region)
234 (Just region)
230− ("The " ++ ordinalPair elementNumber ++ " elements are different types of values.")
235+ ("The " ++ ordinalPair elementNumber ++ " entries in this list are different types of values.")
231 ( cmpHint
236 ( cmpHint
232− ("The " ++ Help.ordinalize (elementNumber - 1) ++ " element has this type:")
237+ ("The " ++ Help.ordinalize (elementNumber - 1) ++ " entry has this type:")
233 ("But the "++ Help.ordinalize elementNumber ++ " is:")
238 ("But the "++ Help.ordinalize elementNumber ++ " is:")
234− [ "All elements should be the same type of value so that we can\
239+ [ "Every entry in a list needs to be the same type of value.\
235− \ iterate through the list without running into unexpected values."
240+ \ This way you never run into unexpected values partway through.\
241+ \ To mix different types in a single list, create a \"union type\" as\
242+ \ described in: <http://guide.elm-lang.org/types/union_types.html>"
@@ -261,14 +268,14 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
261 BinopRight op region ->
268 BinopRight op region ->
263 (Just region)
270 (Just region)
264− ("The right argument of " ++ prettyName op ++ " is causing a type mismatch.")
271+ ("The right side of " ++ prettyName op ++ " is causing a type mismatch.")
265 ( cmpHint
272 ( cmpHint
266− (prettyName op ++ " is expecting the right argument to be a:")
273+ (prettyName op ++ " is expecting the right side to be a:")
267− "But the right argument is:"
274+ "But the right side is:"
268 ( binopHint op leftType rightType
275 ( binopHint op leftType rightType
270− [ "I always figure out the type of the left argument first and if it is\
277+ [ "With binary operations, I always figure out the left side first. If it\
271− \ acceptable on its own, I assume it is \"correct\" in subsequent checks.\
278+ \ seems fine, I assume it is \"correct\" in subsequent checks.\
272 \ So the problem may actually be in how the left and right arguments interact."
279 \ So the problem may actually be in how the left and right arguments interact."
@@ -301,7 +308,7 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
301 UnexpectedArg maybeName 1 1 region ->
308 UnexpectedArg maybeName 1 1 region ->
303 (Just region)
310 (Just region)
304− ("The argument to " ++ funcName maybeName ++ " is causing a mismatch.")
311+ ("The argument to " ++ funcName maybeName ++ " is causing a mismatch.")
305 ( cmpHint
312 ( cmpHint
306 (Help.capitalize (funcName maybeName) ++ " is expecting the argument to be:")
313 (Help.capitalize (funcName maybeName) ++ " is expecting the argument to be:")
307 "But it is:"
314 "But it is:"
@@ -351,24 +358,34 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
351 (text "Maybe you forgot some parentheses? Or a comma?")
358 (text "Maybe you forgot some parentheses? Or a comma?")
353 FunctionArity maybeName expected actual region ->
360 FunctionArity maybeName expected actual region ->
355− s = if expected == 1 then "" else "s"
358− (Just region)
362+ (Just region)
359− ( Help.capitalize (funcName maybeName) ++ " is expecting " ++ show expected
363+ ( Help.capitalize (funcName maybeName) ++ " is expecting "
360− ++ " argument" ++ s ++ ", but was given " ++ show actual ++ "."
364+ ++ Help.args expected ++ ", but was given " ++ show actual ++ "."
362− (text "Maybe you forgot some parentheses? Or a comma?")
366+ (text "Maybe you forgot some parentheses? Or a comma?")
364− BadTypeAnnotation name ->
368+ ReturnType name typeArity argArity region ->
369+ if typeArity == 0 || argArity == 0 then
366− Nothing
371+ (Just region)
367− ("The implementation of " ++ Help.functionName name ++ " does not match its type annotation.")
372+ ("The definition of " ++ Help.functionName name ++ " does not match its type annotation.")
368 ( cmpHint
373 ( cmpHint
369− "The type annotation is saying:"
374+ ( "The type annotation for " ++ Help.functionName name ++ " says it is a:"
370− "But I am inferring that the definition has this type:"
375+ )
371− []
376+ "But the definition (shown above) is a:"
377+ (arityHint typeArity argArity)
383+ ("The definition of " ++ Help.functionName name ++ " does not match its type annotation.")
385+ ( "The type annotation for " ++ Help.functionName name ++ " says it always returns:"
387+ "But the returned value (shown above) is a:"
388+ (arityHint typeArity argArity)
374 Instance name ->
391 Instance name ->
@@ -421,16 +438,6 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
427− "The low and high members of this list range are not the same type of value."
429− "The low end of the range has type:"
430− "But the high end is:"
434 Lambda ->
441 Lambda ->
@@ -441,6 +448,30 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
451+ Access (Just body) field ->
454+ "`" ++ body ++ "` does not have a field named `" ++ field ++ "`."
456+ report Nothing header $ Help.stack $
457+ [ Help.reflowParagraph $ "The type of `" ++ body ++ "` is:"
458+ , indent 4 $ dullyellow $ RenderType.toDoc localizer leftType
459+ , Help.reflowParagraph $ "Which does not contain a field named `" ++ field ++ "`."
463+ Access Nothing field ->
466+ "Cannot access a field named `" ++ field ++ "`."
468+ report Nothing header $ Help.stack $
469+ [ Help.reflowParagraph $ "You are trying to get `" ++ field ++ "` from a value with this type:"
470+ , indent 4 $ dullyellow $ RenderType.toDoc localizer leftType
471+ , Help.reflowParagraph $ "It is not in there!"
444 Record ->
475 Record ->
@@ -563,6 +594,36 @@ functionHint maybeName =
600+arityHint :: Int -> Int -> [String]
601+arityHint typeArity argArity =
602+ if typeArity == argArity then
606+ [ "The type annotation says there " ++ sayArgs typeArity
607+ ++ ", but there " ++ sayArgs argArity ++ " named in the definition."
608+ ++ " It is best practice for each argument in the type to correspond"
609+ ++ " to a named argument in the definition, so try that first!"
613+sayArgs :: Int -> String
623+ "are " ++ show n ++ " arguments"
566-- MISMATCH HELPERS
627-- MISMATCH HELPERS
@@ -593,8 +654,11 @@ funcName maybeVar =
593flipReason :: Reason -> Reason
654flipReason :: Reason -> Reason
594flipReason reason =
655flipReason reason =
595 case reason of
656 case reason of
658+ BadFields (map (second (fmap flipReason)) fields)
596− MessyFields leftOnly rightOnly ->
660+ MessyFields both left right ->
597− MessyFields rightOnly leftOnly
661+ MessyFields both right left
599 IntFloat ->
663 IntFloat ->
@@ -602,24 +666,78 @@ flipReason reason =
602 TooLongComparableTuple len ->
666 TooLongComparableTuple len ->
603 TooLongComparableTuple len
667 TooLongComparableTuple len
605 BadVar left right ->
672 BadVar left right ->
606 BadVar right left
673 BadVar right left
609reasonToString :: Reason -> Maybe Doc
676reasonToString :: Reason -> Maybe Doc
610reasonToString reason =
677reasonToString reason =
679+ (fields, maybeDeepReason) =
680+ collectFields reason
683+ reasonToStringHelp =<< maybeDeepReason
691+ Just $ "Problem in the `" ++ field ++ "` field. "
694+ Just $ "Problem at `" ++ List.intercalate "." fields ++ "`. "
696+ case (starter, maybeDocs) of
697+ (Nothing, Nothing) ->
700+ (Just msg, Nothing) ->
701+ Just $ toHint (msg ++ badFieldElaboration)
703+ (_, Just (firstLine, docs)) ->
705+ toHint (maybe firstLine (++firstLine) starter)
706+ : map (indent 4) docs
710+collectFields :: Reason -> ([String], Maybe Reason)
711+collectFields reason =
713+ BadFields [(field, Nothing)] ->
716+ BadFields [(field, Just subReason)] ->
717+ first (field:) (collectFields subReason)
723+reasonToStringHelp :: Reason -> Maybe (String, [Doc])
724+reasonToStringHelp reason =
613− Just (toHint msg)
727+ Just (msg, [])
615 case reason of
729 case reason of
732+ "I am seeing issues with the "
733+ ++ Help.commaSep (map fst fields) ++ " fields. "
734+ ++ badFieldElaboration
616− MessyFields leftOnly rightOnly ->
736+ MessyFields both leftOnly rightOnly ->
617− do let typos = Help.findPotentialTypos leftOnly rightOnly
737+ messyFieldsHelp both leftOnly rightOnly
618− _ <- Help.vetTypos typos
619− misspellingMessage typos
621 IntFloat ->
739 IntFloat ->
623 "Elm does not automatically convert between Ints and Floats. Use\
741 "Elm does not automatically convert between Ints and Floats. Use\
624 \ `toFloat` and `round` to do specific conversions.\
742 \ `toFloat` and `round` to do specific conversions.\
625 \ <http://package.elm-lang.org/packages/elm-lang/core/latest/Basics#toFloat>"
743 \ <http://package.elm-lang.org/packages/elm-lang/core/latest/Basics#toFloat>"
@@ -629,6 +747,11 @@ reasonToString reason =
629 "Although tuples are comparable, this is currently only supported\
747 "Although tuples are comparable, this is currently only supported\
630 \ for tuples with 6 or fewer entries, not " ++ show len ++ "."
748 \ for tuples with 6 or fewer entries, not " ++ show len ++ "."
752+ "It looks like a function needs " ++ show num ++ " more "
753+ ++ (if num == 1 then "argument" else "arguments") ++ "."
632 BadVar (Just Comparable) _ ->
755 BadVar (Just Comparable) _ ->
633 go "Only ints, floats, chars, strings, lists, and tuples are comparable."
756 go "Only ints, floats, chars, strings, lists, and tuples are comparable."
@@ -638,31 +761,64 @@ reasonToString reason =
638 BadVar (Just CompAppend) _ ->
761 BadVar (Just CompAppend) _ ->
639 go "Only strings and lists are both comparable and appendable."
762 go "Only strings and lists are both comparable and appendable."
641− BadVar (Just (Rigid _)) (Just (Rigid _)) ->
764+ BadVar (Just (Rigid maybeLeftName)) (Just (Rigid maybeRightName)) ->
642− go doubleRigidError
765+ go $ doubleRigidError maybeLeftName maybeRightName
644− BadVar (Just (Rigid _)) _ ->
767+ BadVar (Just (Rigid maybeName)) _ ->
645− go singleRigidError
768+ go $ singleRigidError maybeName
647− BadVar _ (Just (Rigid _)) ->
770+ BadVar _ (Just (Rigid maybeName)) ->
648− go singleRigidError
771+ go $ singleRigidError maybeName
650 BadVar _ _ ->
773 BadVar _ _ ->
654−singleRigidError :: String
777+badFieldElaboration :: String
655−singleRigidError =
778+badFieldElaboration =
656− "A type annotation is too generic. You can probably just switch to the\
779+ "I always figure out field types in alphabetical order. If a field\
657− \ type I inferred. These issues can be subtle though, so read more about it. "
780+ \ seems fine, I assume it is \"correct\" in subsequent checks.\
658− ++ Help.hintLink "type-annotations"
781+ \ So the problem may actually be a weird interaction with previous fields."
661−doubleRigidError :: String
784+weirdRigidError :: String
662−doubleRigidError =
785+weirdRigidError =
663− "A type annotation is clashing with itself or with a sub-annotation.\
786+ "You are having a problem with \"rigid\" type variables. That usually\
664− \ This can be particularly tricky, so read more about it. "
787+ \ means you have a type annotation that says it accepts anything, but\
788+ \ in fact, it accepts a much more specific type. More info at: "
789+ ++ Help.hintLink "type-annotations" ++
790+ ". This case is weird though, so please turn it into an <http://sscce.org>\
791+ \ and report it to <https://github.com/elm-lang/error-message-catalog>."
794+singleRigidError :: Maybe String -> String
795+singleRigidError maybeName =
798+ "Your type annotation uses type variable `" ++ name ++
799+ "` which means any type of value can flow through. Your code is saying\
800+ \ it CANNOT be anything though! Maybe change your type annotation to\
801+ \ be more specific? Maybe the code has a problem? More at: "
802+ ++ Help.hintLink "type-annotations"
808+doubleRigidError :: Maybe String -> Maybe String -> String
809+doubleRigidError maybeLeftName maybeRightName =
810+ case (maybeLeftName, maybeRightName) of
811+ (Just leftName, Just rightName) ->
812+ "Your type annotation uses `" ++ leftName ++ "` and `" ++ rightName ++
813+ "` as separate type variables. Each one can be any type of value,\
814+ \ but because they have separate names, there is no guarantee that they\
815+ \ are equal to each other. Your code seems to be saying they ARE the\
816+ \ same though! Maybe they should be the same in your type annotation?\
817+ \ Maybe your code uses them in a weird way? More at: "
665− ++ Help.hintLink "type-annotations"
818+ ++ Help.hintLink "type-annotations"
668hintDoc :: Doc
824hintDoc :: Doc
@@ -675,28 +831,104 @@ toHint str =
675 fillSep (hintDoc : map text (words str))
831 fillSep (hintDoc : map text (words str))
678−misspellingMessage :: [(String,String)] -> Maybe Doc
834+messyFieldsHelp :: [String] -> [String] -> [String] -> Maybe (String, [Doc])
679−misspellingMessage typos =
835+messyFieldsHelp both leftOnly rightOnly =
680− if null typos then
836+ case (leftOnly, rightOnly) of
681− Nothing
837+ ([], [missingField]) ->
838+ oneMissingField both missingField
683− else
840+ ([missingField], []) ->
841+ oneMissingField both missingField
843+ ([], missingFields) ->
844+ manyMissingFields both missingFields
846+ (missingFields, []) ->
847+ manyMissingFields both missingFields
685− maxLen =
851+ typoPairs =
686− maximum (map (length . fst) typos)
852+ case Help.findTypoPairs leftOnly rightOnly of
854+ Help.findTypoPairs (both ++ leftOnly) (both ++ rightOnly)
859+ if null typoPairs then
861+ ( "The record fields do not match up. One has "
862+ ++ Help.commaSep leftOnly ++ ". The other has "
863+ ++ Help.commaSep rightOnly ++ "."
869+ ( "The record fields do not match up. Maybe you made one of these typos?"
870+ , typoDocs "<->" typoPairs
874+oneMissingField :: [String] -> String -> Maybe (String, [Doc])
875+oneMissingField knownFields missingField =
876+ case Help.findPotentialTypos knownFields missingField of
879+ ( "Looks like a record is missing the `" ++ missingField ++ "` field."
885+ ( "Looks like a record is missing the `" ++ missingField
886+ ++ "` field. Maybe it is a typo?"
887+ , typoDocs "->" [(missingField, typo)]
892+ ( "Looks like a record is missing the `" ++ missingField
893+ ++ "` field. It is close to names like "
894+ ++ Help.commaSep typos ++ " so maybe it is a typo?"
899+manyMissingFields :: [String] -> [String] -> Maybe (String, [Doc])
900+manyMissingFields knownFields missingFields =
901+ case Help.findTypoPairs missingFields knownFields of
688− Just $ hang 4 $ vcat $
903+ Just
689− toHint "I compared the record fields and found some potential typos."
904+ ( "Looks like a record is missing these fields: "
690− : text ""
905+ ++ Help.commaSep missingFields
691− : map (pad maxLen) typos
906+ , []
911+ ( "Looks like a record is missing these fields: " ++ Help.commaSep missingFields
912+ ++ ". Potential typos include:"
913+ , typoDocs "->" typoPairs
917+typoDocs :: String -> [(String, String)] -> [Doc]
918+typoDocs arrow typoPairs =
921+ maximum (map (length . fst) typoPairs)
923+ text "" : map (padTypo arrow maxLen) typoPairs
694−pad :: Int -> (String, String) -> Doc
926+padTypo :: String -> Int -> (String, String) -> Doc
695−pad maxLen (leftField, rightField) =
927+padTypo arrow maxLen (missingField, knownField) =
696− text (replicate (maxLen - length leftField) ' ')
928+ text (replicate (maxLen - length missingField) ' ')
697− <> dullyellow (text leftField)
929+ <> dullyellow (text missingField)
698− <+> text "<->"
930+ <+> text arrow
699− <+> dullyellow (text rightField)
931+ <+> dullyellow (text knownField)