@@ -1,11 +1,13 @@
11{-# OPTIONS_GHC -Wall #-}
22module Reporting.Error.Type where
33
4+import Control.Arrow (first, second)
5+import qualified Data.List as List
46import qualified Data.Map as Map
57import qualified Data.Maybe as Maybe
68import Text.PrettyPrint.ANSI.Leijen
79 ( Doc, (<>), (<+>), colon, dullyellow
8− , fillSep, hang, indent, text, underline, vcat
10+ , fillSep, indent, text, underline, vcat
911 )
1012
1113import qualified AST.Type as Type
@@ -36,9 +38,11 @@ data Mismatch = MismatchInfo
3638
3739
3840data Reason
39− = MessyFields [String] [String]
41+ = BadFields [(String, Maybe Reason)]
42+ | MessyFields [String] [String] [String]
4043 | IntFloat
4144 | TooLongComparableTuple Int
45+ | MissingArgs Int
4246 | BadVar (Maybe VarType) (Maybe VarType)
4347
4448
@@ -65,13 +69,13 @@ data Hint
6569 | Function (Maybe Var.Canonical)
6670 | UnexpectedArg (Maybe Var.Canonical) Int Int Region.Region
6771 | FunctionArity (Maybe Var.Canonical) Int Int Region.Region
68− | BadTypeAnnotation String
72+ | ReturnType String Int Int Region.Region
6973 | Instance String
7074 | Literal String
7175 | Pattern Pattern
7276 | Shader
73− | Range
7477 | Lambda
78+ | Access (Maybe String) String
7579 | Record
7680 -- effect manager problems
7781 | Manager String
@@ -142,11 +146,12 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
142146 report =
143147 Report.report "TYPE MISMATCH"
144148
149+ typicalHints =
150+ Maybe.maybeToList (reasonToString =<< maybeReason)
151+
145152 cmpHint leftWords rightWords extraHints =
146153 comparisonHint localizer leftType rightType leftWords rightWords
147− ( Maybe.maybeToList (reasonToString =<< maybeReason)
148− ++ map toHint extraHints
149− )
154+ (typicalHints ++ map toHint extraHints)
150155 in
151156 case hint of
152157 CaseBranch branchNumber region ->
@@ -227,12 +232,14 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
227232 ListElement elementNumber region ->
228233 report
229234 (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.")
231236 ( cmpHint
232− ("The " ++ Help.ordinalize (elementNumber - 1) ++ " element has this type:")
237+ ("The " ++ Help.ordinalize (elementNumber - 1) ++ " entry has this type:")
233238 ("But the "++ Help.ordinalize elementNumber ++ " is:")
234− [ "All elements should be the same type of value so that we can\
235− \ iterate through the list without running into unexpected values."
239+ [ "Every entry in a list needs to be the same type of value.\
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>"
236243 ]
237244 )
238245
@@ -261,14 +268,14 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
261268 BinopRight op region ->
262269 report
263270 (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.")
265272 ( cmpHint
266− (prettyName op ++ " is expecting the right argument to be a:")
267− "But the right argument is:"
273+ (prettyName op ++ " is expecting the right side to be a:")
274+ "But the right side is:"
268275 ( binopHint op leftType rightType
269276 ++
270− [ "I always figure out the type of the left argument first and if it is\
271− \ acceptable on its own, I assume it is \"correct\" in subsequent checks.\
277+ [ "With binary operations, I always figure out the left side first. If it\
278+ \ seems fine, I assume it is \"correct\" in subsequent checks.\
272279 \ So the problem may actually be in how the left and right arguments interact."
273280 ]
274281 )
@@ -301,7 +308,7 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
301308 UnexpectedArg maybeName 1 1 region ->
302309 report
303310 (Just region)
304− ("The argument to " ++ funcName maybeName ++ " is causing a mismatch.")
311+ ("The argument to " ++ funcName maybeName ++ " is causing a mismatch.")
305312 ( cmpHint
306313 (Help.capitalize (funcName maybeName) ++ " is expecting the argument to be:")
307314 "But it is:"
@@ -351,24 +358,34 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
351358 (text "Maybe you forgot some parentheses? Or a comma?")
352359
353360 FunctionArity maybeName expected actual region ->
354− let
355− s = if expected == 1 then "" else "s"
356− in
357− report
358− (Just region)
359− ( Help.capitalize (funcName maybeName) ++ " is expecting " ++ show expected
360− ++ " argument" ++ s ++ ", but was given " ++ show actual ++ "."
361− )
362− (text "Maybe you forgot some parentheses? Or a comma?")
361+ report
362+ (Just region)
363+ ( Help.capitalize (funcName maybeName) ++ " is expecting "
364+ ++ Help.args expected ++ ", but was given " ++ show actual ++ "."
365+ )
366+ (text "Maybe you forgot some parentheses? Or a comma?")
363367
364− BadTypeAnnotation name ->
368+ ReturnType name typeArity argArity region ->
369+ if typeArity == 0 || argArity == 0 then
365370 report
366− Nothing
367− ("The implementation of " ++ Help.functionName name ++ " does not match its type annotation.")
371+ (Just region)
372+ ("The definition of " ++ Help.functionName name ++ " does not match its type annotation.")
368373 ( cmpHint
369− "The type annotation is saying:"
370− "But I am inferring that the definition has this type:"
371− []
374+ ( "The type annotation for " ++ Help.functionName name ++ " says it is a:"
375+ )
376+ "But the definition (shown above) is a:"
377+ (arityHint typeArity argArity)
378+ )
379+
380+ else
381+ report
382+ (Just region)
383+ ("The definition of " ++ Help.functionName name ++ " does not match its type annotation.")
384+ ( cmpHint
385+ ( "The type annotation for " ++ Help.functionName name ++ " says it always returns:"
386+ )
387+ "But the returned value (shown above) is a:"
388+ (arityHint typeArity argArity)
372389 )
373390
374391 Instance name ->
@@ -421,16 +438,6 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
421438 []
422439 )
423440
424− Range ->
425− report
426− Nothing
427− "The low and high members of this list range are not the same type of value."
428− ( cmpHint
429− "The low end of the range has type:"
430− "But the high end is:"
431− []
432− )
433−
434441 Lambda ->
435442 report
436443 Nothing
@@ -441,6 +448,30 @@ mismatchToReport localizer (MismatchInfo hint leftType rightType maybeReason) =
441448 []
442449 )
443450
451+ Access (Just body) field ->
452+ let
453+ header =
454+ "`" ++ body ++ "` does not have a field named `" ++ field ++ "`."
455+ in
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 ++ "`."
460+ ]
461+ ++ typicalHints
462+
463+ Access Nothing field ->
464+ let
465+ header =
466+ "Cannot access a field named `" ++ field ++ "`."
467+ in
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!"
472+ ]
473+ ++ typicalHints
474+
444475 Record ->
445476 report
446477 Nothing
@@ -563,6 +594,36 @@ functionHint maybeName =
563594
564595
565596
597+-- ARITY HINTS
598+
599+
600+arityHint :: Int -> Int -> [String]
601+arityHint typeArity argArity =
602+ if typeArity == argArity then
603+ []
604+
605+ else
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!"
610+ ]
611+
612+
613+sayArgs :: Int -> String
614+sayArgs n =
615+ case n of
616+ 0 ->
617+ "are NO arguments"
618+
619+ 1 ->
620+ "is 1 argument"
621+
622+ _ ->
623+ "are " ++ show n ++ " arguments"
624+
625+
626+
566627-- MISMATCH HELPERS
567628
568629
@@ -593,8 +654,11 @@ funcName maybeVar =
593654flipReason :: Reason -> Reason
594655flipReason reason =
595656 case reason of
596− MessyFields leftOnly rightOnly ->
597− MessyFields rightOnly leftOnly
657+ BadFields fields ->
658+ BadFields (map (second (fmap flipReason)) fields)
659+
660+ MessyFields both left right ->
661+ MessyFields both right left
598662
599663 IntFloat ->
600664 IntFloat
@@ -602,24 +666,78 @@ flipReason reason =
602666 TooLongComparableTuple len ->
603667 TooLongComparableTuple len
604668
669+ MissingArgs num ->
670+ MissingArgs num
671+
605672 BadVar left right ->
606673 BadVar right left
607674
608675
609676reasonToString :: Reason -> Maybe Doc
610677reasonToString reason =
678+ let
679+ (fields, maybeDeepReason) =
680+ collectFields reason
681+
682+ maybeDocs =
683+ reasonToStringHelp =<< maybeDeepReason
684+
685+ starter =
686+ case fields of
687+ [] ->
688+ Nothing
689+
690+ [field] ->
691+ Just $ "Problem in the `" ++ field ++ "` field. "
692+
693+ _ ->
694+ Just $ "Problem at `" ++ List.intercalate "." fields ++ "`. "
695+ in
696+ case (starter, maybeDocs) of
697+ (Nothing, Nothing) ->
698+ Nothing
699+
700+ (Just msg, Nothing) ->
701+ Just $ toHint (msg ++ badFieldElaboration)
702+
703+ (_, Just (firstLine, docs)) ->
704+ Just $ vcat $
705+ toHint (maybe firstLine (++firstLine) starter)
706+ : map (indent 4) docs
707+
708+
709+
710+collectFields :: Reason -> ([String], Maybe Reason)
711+collectFields reason =
712+ case reason of
713+ BadFields [(field, Nothing)] ->
714+ ([field], Nothing)
715+
716+ BadFields [(field, Just subReason)] ->
717+ first (field:) (collectFields subReason)
718+
719+ _ ->
720+ ([], Just reason)
721+
722+
723+reasonToStringHelp :: Reason -> Maybe (String, [Doc])
724+reasonToStringHelp reason =
611725 let
612726 go msg =
613− Just (toHint msg)
727+ Just (msg, [])
614728 in
615729 case reason of
616− MessyFields leftOnly rightOnly ->
617− do let typos = Help.findPotentialTypos leftOnly rightOnly
618− _ <- Help.vetTypos typos
619− misspellingMessage typos
730+ BadFields fields ->
731+ go $
732+ "I am seeing issues with the "
733+ ++ Help.commaSep (map fst fields) ++ " fields. "
734+ ++ badFieldElaboration
735+
736+ MessyFields both leftOnly rightOnly ->
737+ messyFieldsHelp both leftOnly rightOnly
620738
621739 IntFloat ->
622− go
740+ go $
623741 "Elm does not automatically convert between Ints and Floats. Use\
624742 \ `toFloat` and `round` to do specific conversions.\
625743 \ <http://package.elm-lang.org/packages/elm-lang/core/latest/Basics#toFloat>"
@@ -629,6 +747,11 @@ reasonToString reason =
629747 "Although tuples are comparable, this is currently only supported\
630748 \ for tuples with 6 or fewer entries, not " ++ show len ++ "."
631749
750+ MissingArgs num ->
751+ go $
752+ "It looks like a function needs " ++ show num ++ " more "
753+ ++ (if num == 1 then "argument" else "arguments") ++ "."
754+
632755 BadVar (Just Comparable) _ ->
633756 go "Only ints, floats, chars, strings, lists, and tuples are comparable."
634757
@@ -638,31 +761,64 @@ reasonToString reason =
638761 BadVar (Just CompAppend) _ ->
639762 go "Only strings and lists are both comparable and appendable."
640763
641− BadVar (Just (Rigid _)) (Just (Rigid _)) ->
642− go doubleRigidError
764+ BadVar (Just (Rigid maybeLeftName)) (Just (Rigid maybeRightName)) ->
765+ go $ doubleRigidError maybeLeftName maybeRightName
643766
644− BadVar (Just (Rigid _)) _ ->
645− go singleRigidError
767+ BadVar (Just (Rigid maybeName)) _ ->
768+ go $ singleRigidError maybeName
646769
647− BadVar _ (Just (Rigid _)) ->
648− go singleRigidError
770+ BadVar _ (Just (Rigid maybeName)) ->
771+ go $ singleRigidError maybeName
649772
650773 BadVar _ _ ->
651774 Nothing
652775
653776
654−singleRigidError :: String
655−singleRigidError =
656− "A type annotation is too generic. You can probably just switch to the\
657− \ type I inferred. These issues can be subtle though, so read more about it. "
658− ++ Help.hintLink "type-annotations"
777+badFieldElaboration :: String
778+badFieldElaboration =
779+ "I always figure out field types in alphabetical order. If a field\
780+ \ seems fine, I assume it is \"correct\" in subsequent checks.\
781+ \ So the problem may actually be a weird interaction with previous fields."
659782
660783
661−doubleRigidError :: String
662−doubleRigidError =
663− "A type annotation is clashing with itself or with a sub-annotation.\
664− \ This can be particularly tricky, so read more about it. "
665− ++ Help.hintLink "type-annotations"
784+weirdRigidError :: String
785+weirdRigidError =
786+ "You are having a problem with \"rigid\" type variables. That usually\
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>."
792+
793+
794+singleRigidError :: Maybe String -> String
795+singleRigidError maybeName =
796+ case maybeName of
797+ Just name ->
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"
803+
804+ Nothing ->
805+ weirdRigidError
806+
807+
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: "
818+ ++ Help.hintLink "type-annotations"
819+
820+ _ ->
821+ weirdRigidError
666822
667823
668824hintDoc :: Doc
@@ -675,28 +831,104 @@ toHint str =
675831 fillSep (hintDoc : map text (words str))
676832
677833
678−misspellingMessage :: [(String,String)] -> Maybe Doc
679−misspellingMessage typos =
680− if null typos then
681− Nothing
834+messyFieldsHelp :: [String] -> [String] -> [String] -> Maybe (String, [Doc])
835+messyFieldsHelp both leftOnly rightOnly =
836+ case (leftOnly, rightOnly) of
837+ ([], [missingField]) ->
838+ oneMissingField both missingField
682839
683− else
840+ ([missingField], []) ->
841+ oneMissingField both missingField
842+
843+ ([], missingFields) ->
844+ manyMissingFields both missingFields
845+
846+ (missingFields, []) ->
847+ manyMissingFields both missingFields
848+
849+ _ ->
684850 let
685− maxLen =
686− maximum (map (length . fst) typos)
851+ typoPairs =
852+ case Help.findTypoPairs leftOnly rightOnly of
853+ [] ->
854+ Help.findTypoPairs (both ++ leftOnly) (both ++ rightOnly)
855+
856+ pairs ->
857+ pairs
687858 in
688− Just $ hang 4 $ vcat $
689− toHint "I compared the record fields and found some potential typos."
690− : text ""
691− : map (pad maxLen) typos
692−
693−
694−pad :: Int -> (String, String) -> Doc
695−pad maxLen (leftField, rightField) =
696− text (replicate (maxLen - length leftField) ' ')
697− <> dullyellow (text leftField)
698− <+> text "<->"
699− <+> dullyellow (text rightField)
859+ if null typoPairs then
860+ Just
861+ ( "The record fields do not match up. One has "
862+ ++ Help.commaSep leftOnly ++ ". The other has "
863+ ++ Help.commaSep rightOnly ++ "."
864+ , []
865+ )
866+
867+ else
868+ Just
869+ ( "The record fields do not match up. Maybe you made one of these typos?"
870+ , typoDocs "<->" typoPairs
871+ )
872+
873+
874+oneMissingField :: [String] -> String -> Maybe (String, [Doc])
875+oneMissingField knownFields missingField =
876+ case Help.findPotentialTypos knownFields missingField of
877+ [] ->
878+ Just
879+ ( "Looks like a record is missing the `" ++ missingField ++ "` field."
880+ , []
881+ )
882+
883+ [typo] ->
884+ Just
885+ ( "Looks like a record is missing the `" ++ missingField
886+ ++ "` field. Maybe it is a typo?"
887+ , typoDocs "->" [(missingField, typo)]
888+ )
889+
890+ typos ->
891+ Just
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?"
895+ , []
896+ )
897+
898+
899+manyMissingFields :: [String] -> [String] -> Maybe (String, [Doc])
900+manyMissingFields knownFields missingFields =
901+ case Help.findTypoPairs missingFields knownFields of
902+ [] ->
903+ Just
904+ ( "Looks like a record is missing these fields: "
905+ ++ Help.commaSep missingFields
906+ , []
907+ )
908+
909+ typoPairs ->
910+ Just
911+ ( "Looks like a record is missing these fields: " ++ Help.commaSep missingFields
912+ ++ ". Potential typos include:"
913+ , typoDocs "->" typoPairs
914+ )
915+
916+
917+typoDocs :: String -> [(String, String)] -> [Doc]
918+typoDocs arrow typoPairs =
919+ let
920+ maxLen =
921+ maximum (map (length . fst) typoPairs)
922+ in
923+ text "" : map (padTypo arrow maxLen) typoPairs
924+
925+
926+padTypo :: String -> Int -> (String, String) -> Doc
927+padTypo arrow maxLen (missingField, knownField) =
928+ text (replicate (maxLen - length missingField) ' ')
929+ <> dullyellow (text missingField)
930+ <+> text arrow
931+ <+> dullyellow (text knownField)
700932
701933
702934