Improving record type errors

Now that golden tests were merged in, I moved back to the original issue but I have a couple of questions regarding the hints that are generated in case of a TypesDoNotUnify error.

Using this code a = [{ a: 1, b: 2 }, { a: 1 }], these are the hints before stripRedundantHints is called:

[ ErrorUnifyingTypes
    (RCons
       ( SourceSpan
           { spanName = ""
           , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
           , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
           }
       , []
       )
       Label { runLabel = "a" }
       (TypeConstructor
          ( SourceSpan
              { spanName = ""
              , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              }
          , []
          )
          (Qualified
             (Just (ModuleName [ ProperName { runProperName = "Prim" } ]))
             ProperName { runProperName = "Int" }))
       (RCons
          ( SourceSpan
              { spanName = ""
              , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              }
          , []
          )
          Label { runLabel = "b" }
          (TypeConstructor
             ( SourceSpan
                 { spanName = ""
                 , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
                 , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
                 }
             , []
             )
             (Qualified
                (Just (ModuleName [ ProperName { runProperName = "Prim" } ]))
                ProperName { runProperName = "Int" }))
          (REmpty
             ( SourceSpan
                 { spanName = ""
                 , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
                 , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
                 }
             , []
             ))))
    (RCons
       ( SourceSpan
           { spanName = ""
           , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
           , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
           }
       , []
       )
       Label { runLabel = "a" }
       (TypeConstructor
          ( SourceSpan
              { spanName = ""
              , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              }
          , []
          )
          (Qualified
             (Just (ModuleName [ ProperName { runProperName = "Prim" } ]))
             ProperName { runProperName = "Int" }))
       (REmpty
          ( SourceSpan
              { spanName = ""
              , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              }
          , []
          )))
, ErrorUnifyingTypes
    (RCons
       ( SourceSpan
           { spanName = ""
           , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
           , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
           }
       , []
       )
       Label { runLabel = "a" }
       (TypeConstructor
          ( SourceSpan
              { spanName = ""
              , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              }
          , []
          )
          (Qualified
             (Just (ModuleName [ ProperName { runProperName = "Prim" } ]))
             ProperName { runProperName = "Int" }))
       (RCons
          ( SourceSpan
              { spanName = ""
              , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              }
          , []
          )
          Label { runLabel = "b" }
          (TypeConstructor
             ( SourceSpan
                 { spanName = ""
                 , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
                 , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
                 }
             , []
             )
             (Qualified
                (Just (ModuleName [ ProperName { runProperName = "Prim" } ]))
                ProperName { runProperName = "Int" }))
          (REmpty
             ( SourceSpan
                 { spanName = ""
                 , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
                 , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
                 }
             , []
             ))))
    (RCons
       ( SourceSpan
           { spanName = ""
           , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
           , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
           }
       , []
       )
       Label { runLabel = "a" }
       (TypeConstructor
          ( SourceSpan
              { spanName = ""
              , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              }
          , []
          )
          (Qualified
             (Just (ModuleName [ ProperName { runProperName = "Prim" } ]))
             ProperName { runProperName = "Int" }))
       (REmpty
          ( SourceSpan
              { spanName = ""
              , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              }
          , []
          )))
, ErrorUnifyingTypes
    (TUnknown
       ( SourceSpan
           { spanName = ""
           , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
           , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
           }
       , []
       )
       2)
    (TypeApp
       ( SourceSpan
           { spanName = ""
           , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
           , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
           }
       , []
       )
       (TypeConstructor
          ( SourceSpan
              { spanName = ""
              , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              }
          , []
          )
          (Qualified
             (Just (ModuleName [ ProperName { runProperName = "Prim" } ]))
             ProperName { runProperName = "Record" }))
       (RCons
          ( SourceSpan
              { spanName = ""
              , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
              }
          , []
          )
          Label { runLabel = "a" }
          (TypeConstructor
             ( SourceSpan
                 { spanName = ""
                 , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
                 , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
                 }
             , []
             )
             (Qualified
                (Just (ModuleName [ ProperName { runProperName = "Prim" } ]))
                ProperName { runProperName = "Int" }))
          (REmpty
             ( SourceSpan
                 { spanName = ""
                 , spanStart = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
                 , spanEnd = SourcePos { sourcePosLine = 0 , sourcePosColumn = 0 }
                 }
             , []
             ))))
, PositionedError
    (SourceSpan
       { spanName = "tests/purs/passing/2138.purs"
       , spanStart = SourcePos { sourcePosLine = 7 , sourcePosColumn = 5 }
       , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 31 }
       } :|
       [])
, ErrorInferringType
    (PositionedValue
       SourceSpan
         { spanName = "tests/purs/passing/2138.purs"
         , spanStart = SourcePos { sourcePosLine = 7 , sourcePosColumn = 5 }
         , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 31 }
         }
       []
       (Literal
          SourceSpan
            { spanName = "tests/purs/passing/2138.purs"
            , spanStart = SourcePos { sourcePosLine = 7 , sourcePosColumn = 5 }
            , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 31 }
            }
          (ArrayLiteral
             [ PositionedValue
                 SourceSpan
                   { spanName = "tests/purs/passing/2138.purs"
                   , spanStart = SourcePos { sourcePosLine = 7 , sourcePosColumn = 6 }
                   , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 20 }
                   }
                 []
                 (Literal
                    SourceSpan
                      { spanName = "tests/purs/passing/2138.purs"
                      , spanStart = SourcePos { sourcePosLine = 7 , sourcePosColumn = 6 }
                      , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 20 }
                      }
                    (ObjectLiteral
                       [ ( "a"
                         , PositionedValue
                             SourceSpan
                               { spanName = "tests/purs/passing/2138.purs"
                               , spanStart =
                                   SourcePos { sourcePosLine = 7 , sourcePosColumn = 11 }
                               , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 12 }
                               }
                             []
                             (Literal
                                SourceSpan
                                  { spanName = "tests/purs/passing/2138.purs"
                                  , spanStart =
                                      SourcePos { sourcePosLine = 7 , sourcePosColumn = 11 }
                                  , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 12 }
                                  }
                                (NumericLiteral (Left 1)))
                         )
                       , ( "b"
                         , PositionedValue
                             SourceSpan
                               { spanName = "tests/purs/passing/2138.purs"
                               , spanStart =
                                   SourcePos { sourcePosLine = 7 , sourcePosColumn = 17 }
                               , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 18 }
                               }
                             []
                             (Literal
                                SourceSpan
                                  { spanName = "tests/purs/passing/2138.purs"
                                  , spanStart =
                                      SourcePos { sourcePosLine = 7 , sourcePosColumn = 17 }
                                  , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 18 }
                                  }
                                (NumericLiteral (Left 2)))
                         )
                       ]))
             , PositionedValue
                 SourceSpan
                   { spanName = "tests/purs/passing/2138.purs"
                   , spanStart =
                       SourcePos { sourcePosLine = 7 , sourcePosColumn = 22 }
                   , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 30 }
                   }
                 []
                 (Literal
                    SourceSpan
                      { spanName = "tests/purs/passing/2138.purs"
                      , spanStart =
                          SourcePos { sourcePosLine = 7 , sourcePosColumn = 22 }
                      , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 30 }
                      }
                    (ObjectLiteral
                       [ ( "a"
                         , PositionedValue
                             SourceSpan
                               { spanName = "tests/purs/passing/2138.purs"
                               , spanStart =
                                   SourcePos { sourcePosLine = 7 , sourcePosColumn = 27 }
                               , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 28 }
                               }
                             []
                             (Literal
                                SourceSpan
                                  { spanName = "tests/purs/passing/2138.purs"
                                  , spanStart =
                                      SourcePos { sourcePosLine = 7 , sourcePosColumn = 27 }
                                  , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 28 }
                                  }
                                (NumericLiteral (Left 1)))
                         )
                       ]))
             ])))
, PositionedError
    (SourceSpan
       { spanName = "tests/purs/passing/2138.purs"
       , spanStart = SourcePos { sourcePosLine = 7 , sourcePosColumn = 1 }
       , spanEnd = SourcePos { sourcePosLine = 7 , sourcePosColumn = 31 }
       } :|
       [])
, ErrorInValueDeclaration (Ident "a")
, ErrorInModule
    (ModuleName [ ProperName { runProperName = "Main" } ])
]

Which, if I’m not wrong, simplifies to (using some pseudo-code):

[ ErrorUnifyingTypes ( a :: Int, b :: Int ) ( a :: Int )
, ErrorUnifyingTypes ( a :: Int, b :: Int ) ( a :: Int )
, ErrorUnifyingTypes TUnknown (a :: Int)
, PositionedError ...
, ErrorInferringType ...
, PositionedError ...
, ErrorInValueDeclaration ...
, ErrorInModule
]

The first two hints are apparently always repeated, and that’s why they’re passed through stripRedundantHints.


What I’m doing now is removing the other ErrorUnifyingTypes hint when it matches the TypesDoNotUnify error.

The first problem is that I’m left with that TUnknown hint. I can arbitrarly remove it, and it works fine in this situation, but I’m moderately sure that it would mess up cases where the type is actually uknown.

The second problem is that with nested records the “list of hints” goes something like this:
[(b :: Int) (c :: Int), { b :: Int } { c :: Int }, { a :: { b :: Int } } { a :: { c :: Int } }].
I can remove the the first hint by matching types, and the second one by extracting the row from the record, but then it gets trickier (and worse in case of deeper nestings, I guess).

I guess an hackish solution could be removing all the ErrorUnifyingTypes hints in case the first one matches the error, but I have no idea whether this is going to mess other things up (and I don’t know whether there are enough golden tests to cover this).

The other solution would be fixing the produced hints, but before going down that path I’d rather have some opinions on this first!

p.s. to debug this I’ve temporarily added pretty-show as a dependency, it made my life 100x easier :slight_smile:

Sorry to bother you guys @hdgarrood @natefaubion , but I wonder if you could direct me towards the best solution here when you have some time! Thanks a lot :slight_smile:

Take everything I’m about to say with a grain of salt as I haven’t really worked on the type checker yet :sweat_smile:

I think in general, a hint of the form ErrorUnifyingTypes TUnknown _ is potentially a cause for concern, as unknown types should be able to unify with everything. Perhaps the type checker shouldn’t be generating these hints in the first place. I think removing them might be fine; they don’t really give you much information anyway.

This doesn’t seem like it’s necessarily a problem? There is some duplicated information, but each hint does give you some new information which the previous ones don’t have.

I think there probably is a fair bit of work we could/should do inside the type checker to improve error messages, so this sounds good to me. As I say I’m not sure how helpful I can be with this at the moment though. I want to set some time aside to familiarise myself with the type checker and how error messages work once 0.14 is out and the infrastructure and packaging situations have calmed down a bit.

Thanks for the answer!

I’ll try to explain better the second problem regarding nested records. Sometimes it’s a bit hard to be clear enough writing in english, sorry!

With the approach I’m using, when I get a “matching” ErrorUnifyingTypes hint and TypesDoNotUnify error I remove the hint, and this makes the redundant while trying to match type disappear correctly.

With nested records, though, there’s a hint for each nesting level. If I keep that approach, I just remove the first hint, but keep all the hints for the subsequent “levels”. This means that the unifying hint is still shown.

For example, with this two records {a :: { b:: Int } } and {a :: { c :: Int } } the hint would be about matching the whole records instead of just (b :: Int) with (c :: Int) like it happens currently.

This feels very arbitrary and not something that we’d want, imho.

Two possible approaches I can think are:

  1. Don’t strip the unifying hint in case it happens inside a nested record (but I think this would leave a lot of cases untouched)
  2. In case the first ErrorUnifyingTypes hint and the TypesDoNotUnify error matches, strip all the unifying hints.

I think it just means the unknown wasn’t fully substituted in the hint before it got shown.

That probably shouldn’t happen, right?

Ok so the approach I’m going to take is:

  • Check whether the first unify hint matches the error
  • In that case remove the first hint
  • Check the following unify hints and remove the one regarding the unknown type (which is always the last one)

The only thing missing is how to handle nested records. I’m making another example:

b = [{ a: { b: { c: { foo: 1, bar: true } } } }, { a: { b: { c: { foo: 1, baz: false } } } }]

The error here is:

Could not match type 

  ( bar :: Boolean
  ...
  )

with type

  ( baz :: Boolean
  ...
  )

As it is right now (and with the approach I wrote on top of this message the hint would be

while trying to match type { bar :: Boolean
                           , foo :: int
                           }
  with type { baz :: Boolean
            , foo :: Int
            }

I can go up one level with the hint, which I feel it’s clearer, making the hint:

while trying to match type ( c:: { bar :: Boolean
                                 , foo :: Int
                                 }
                           ...
                           )
with type (c :: { baz :: Boolean
                , foo :: Int
                }
          ...
          )

Or I can remove all the hints completely.

I think there’s no clear winner here probably, as it depends on how deeply nested the error is, and thus how much the informations contained in the TypesDoNotUnify and ErrorInferringType errors overlap.

2 Likes