danidiaz / by-other-names

Give aliases to record fields
BSD 3-Clause "New" or "Revised" License
2 stars 0 forks source link

Improve error messages with TypeErrors #1

Open danidiaz opened 4 years ago

danidiaz commented 4 years ago

Look in A story told by Type Errors for inspiration.

Here's what the GHC User Guide says about them.

danidiaz commented 4 years ago

Added a type error for mismatched names in db0194449e5a7b327111ddb80b20e066ddc1251e, but more could be added.

For example, the error for forgetting to add the alias for the last branch of a sum type is:

    * No instance for (ByOtherNames.AliasTree
                         '[]
                         (M1
                            C
                            ('MetaCons "Ee" 'PrefixI 'False)
                            (S1
                               ('MetaSel
                                  'Nothing 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy)
                               (Rec0 Int)))
                         '[])

And the error for forgetting to add the alias for the last field of a record type is:

tests\tests.hs:38:5: error:
    * No instance for (ByOtherNames.AliasTree
                         '[]
                         (M1
                            S
                            ('MetaSel
                               ('Just "ee")
                               'NoSourceUnpackedness
                               'NoSourceStrictness
                               'DecidedLazy)
                            (Rec0 Int))
                         '[])

Which could be better.

danidiaz commented 4 years ago

Added a type error for missing final aliases in 4985c0ab8f32ddb87aa2687f39c8e0a3d2aeb98f.

Other possible errors to improve:

tests\tests.hs:38:5: error:
    * No instance for (ByOtherNames.AliasTree
                         middle3
                         (M1
                            S
                            ('MetaSel
                               'Nothing 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy)
                            (Rec0 Bool))
                         middle0)
        arising from a use of `fieldAliases'
    * No instance for (ByOtherNames.AliasTree
                         '["Bb", "Cc", "Dd", "Ee"]
                         (M1
                            C
                            ('MetaCons "Bb" 'PrefixI 'True)
                            (S1
                               ('MetaSel
                                  ('Just "fofofo")
                                  'NoSourceUnpackedness
                                  'NoSourceStrictness
                                  'DecidedLazy)
                               (Rec0 Bool)))
danidiaz commented 4 years ago

Sometimes the presence of the functional dependency gets in the way of improving the errors:

These two instances can't coexist:

instance AliasTree before tree '[] => AliasTree before (D1 x (C1 y tree)) '[] where
  parseAliasTree as =
    let (aliases', as') = parseAliasTree as
     in (Record aliases', as')

-- doesn't work because of the functional dependency :(
-- instance ExcessAliasError name => AliasTree before (D1 x (C1 y tree)) (name : names) where
danidiaz commented 1 year ago

Sometimes the presence of the functional dependency gets in the way of improving the errors

This is even worse in the case of ByOtherNamesH. There, the dependency needs to be bidirectional in order for type inference to work, but it precludes all the custom error messages implemented in ByOtherNames.

type ToAliases :: [(Symbol, [Type])] -> (Type -> Type) -> [(Symbol, [Type])] -> Constraint
-- | The second functional dependency is needed for type inference to work. 
class ToAliases before rep after | before rep -> after, after rep -> before where
  parseAliasTree :: AliasList before a h -> (Aliases rep a h, AliasList after a h)

type ToBranchFields :: [Type] -> (Type -> Type) -> [Type] -> Constraint 
-- | The second functional dependency is needed for type inference to work. 
class ToBranchFields before rep after | before rep -> after, after rep -> before where
  parseBranchFields :: SlotList before h -> (BranchFields rep h, SlotList after h)