Convert diagnostics in `GHC.Tc.Validity` to proper `TcRnMessage`
Short Summary: Get rid of the usages of
Do so by creating new type constructors for the
TcRnMessage type, specific to
the diagnostics you are converting.
There are various occurrences of
TcRnUnknownMessage in the following modules:
These occurrences takes a
SDoc as input and produces a
TcRnMessage, but they carry
very little information and structure with them, which means that GHC API users can't
meaningfully derive much from the
SDoc unless it's parsed or analysed.
We would like to replace these with new type constructors to be added to the
type, which would give us a precise (and structured) semantic of the diagnostics in question.
To give you a concrete example of a successfully ported message, we want to go from something like this:
localPatternSynonymErr :: TcRnMessage localPatternSynonymErr = TcRnUnknownMessage $ mkPlainError noHints $ hang (text "Illegal pattern synonym declaration for" <+> quotes (ppr rdrname)) 2 (text "Pattern synonym declarations are only valid at top level")
to something like this:
localPatternSynonymErr = TcRnIllegalPatSynDecl rdrname
TcRnIllegalPatSynDecl is defined as:
data TcRnMessage = ... | TcRnIllegalPatSynDecl :: !(LIdP GhcPs) -> TcRnMessage ...
Possible plan of action
What follows is a possible plan of action, to guide the developer working on this:
Add as many type constructors as needed to
GHC.Tc.Errors.Types.TcRnMessage. If possible, try to document these following the schema used for the existing ones. It might be possible there is already a type constructor readily available to use, so check for its presence.
This is a good time to grep the codebase for the text of the diagnostics you want to port, which should hopefully yield a number of test cases. You can later run the testsuite on those tests locally (e.g.
./hadrian/build test --only=...), to verify the final output didn't change. Occasionally the final output might change, for example if new hints are added. Don't be afraid to amend the tests in case that happens, and use your best judgement. The goal is to port existing messages to a new infrastructure, not necessarily improve GHC's diagnostic English prose. That can happen at a later stage.
Occasionally some instances of
TcRnUnknownMessagemight be wrapping
SDocs which are generated from other
SDocs (perhaps even passed as input in a function that produces yet another
SDoc). All those little
SDocfragments should be converted into proper Haskell types so that we would be passing to new type constructors not one or more
SDocs but richer Haskell types (random examples:
HsBind GhcPs, etc).
In rare circumstances it might be not feasible to pass a proper Haskell type, maybe due to some nasty cyclic import. If that happens, it's passable (but not ideal) to do something like this:
... TcRnNewConstructor :: Outputable a => ComplexTypeThatTakesAn a -> ... -> TcRnMessage
GHC will now complain that the
instance Diagnostic TcRnMessageinside
GHC.Tc.Errors.Ppris not complete, if new type constructors were added. The
diagnosticHintswill need to handle the new type constructors.
diagnosticMessageby copying the original textual representation of the diagnostic message from the ported module. If the textual representation is using external data (i.e. local binds in that module) add the necessary type arguments to the constructor to "carry the data over". Again, follow the existing patterns.
diagnosticReasonby copying the original
DiagnosticReason(s)from the original module, where the message was produced originally.
diagnosticHintsby copying, once again, what the original diagnostic in ported module is doing. If there are no hints, return
noHints. Be aware that hints might be tucked and hidden into the English prose of the diagnostic you are porting, in which case you might have to create a brand new
GhcHinttype constructor if none of the existing ones captures the hint.