Add solutions C and D authored by Sebastian Graf's avatar Sebastian Graf
......@@ -24,6 +24,8 @@ The key solution is to move source locations to the extension points, remove the
1. We put the source locations in the new constructor extension, similar in spirit to the current `Located`.
1. We put the source locations in the new field extensions and use a typeclass to set/get the locations.
1. We improve on 1. by recovering the ping-pong style for its favorable type safety, still inside the same TTG encoding, by making sure that `XPat` is only possible in `LPat` and not in plain `Pat`.
1. We call a type family in `LPat` that expands to `Located Pat` for `GhcPass`es and to `Pat` otherwise.
In the implementation, we have settled on the solution A, mainly as it avoids the clutter: handling of source locations is done once per data type rather than once in every constructor.
......@@ -173,6 +175,8 @@ Pros:
Cons:
- At the binding site of a variable we know that we \*always\* have a location, and we can put that in its Name. If locations were more optional, that would not be so true.
- Type safety: There are functions like `collectEvVarsPat` and `hsPatType` which return wrong results or crash when passed an `XPat`. Which the type-checker can't detect, since `type LPat = Pat`.
- There are two indirections instead of only one for the GHC case compared to `type LPat = Located Pat`: One for the `XPat` and one for `L`.
### Solution B: the source locations in the new field extensions
......@@ -188,6 +192,52 @@ Cons:
- Handling of the source locations should be done once per constructor
- When constructing/generating terms the first field of the constructors should explicitly mention the source location (see the `par` function in the Solution A's code, where the first field of `Par` should have a `SrcSpan`, even though a dummy one.)
### Solution C: Improving A by re-introducing ping-pong style for type safety
This is implemented in !1925. The gist was to define
```haskell
data Loc p
type LPat p = Pat (Loc p)
```
and then have
```haskell
type instance XWildPat = NoExtField
...
type instance XPat GhcTc = NoExtCon
type instance XWildPat (Loc p) = NoExtCon
...
type instance XPat (Loc p) = Located (Pat p)
```
Pros:
- Same type safety guarantees as ping-pong style. No way to forget to attach a `SrcLoc` to an `LPat`, no way to forget to match on `XPat` in `Pat` position (think of legacy code like `collectEvVarsPat` that would be broken)
- Mostly same performance guarantees as solution A for GHC code
Cons:
- Two indirections (`XPat`, `Located`) to traverse in the GHC AST. The same as solution A, but we don't have the cleverness in the `HasSrcSpan` instance that can get rid of `XPat`s wrapping a `noSrcLoc`. Also this is strictly worse than the `type LPat = Located Pat` approach.
- An unnecessary indirection in case we re-use the AST for TH: Every `LPat` must be an `XPat`, which would just carry a `Pat` again for TH. Ping-pong without a point.
### Solution D
Have `type LPat = Located Pat` for GHC passes (so what we used to have) and `type LPat = Pat` for other passes, by using a type family `WrapL` that only inserts `Located` in GHC passes.
Pros:
- The old ping-pong style! Type safety!
- Only one indirection in recursive `LPat` cases (the `Located` constructor) in the GHC case compared to two for solutions A and C.
- Zero indirections for TH. No need to bother with `Located` at all.
Cons:
- Potential trouble with inference of type family arguments. The type family has to be injective, which can be guaranteed in practice
## An example to illustrate
......
......