Avoid unrepresentable InfixCon state in GADT constructors
Currently, the ASTs for both Haskell98-style data constructors and GADT contructors share the same data type for representing their arguments:
data ConDecl pass
= ConDeclGADT
{ ...
, con_args :: HsConDeclDetails pass -- ^ Arguments; never InfixCon
, ...
}
| ConDeclH98
{ ...
, con_args :: HsConDeclDetails pass -- ^ Arguments; can be InfixCon
, ...
}
type HsConDeclDetails pass
= HsConDetails (HsScaled pass (LBangType pass)) (Located [LConDeclField pass])
And:
data HsConDetails arg rec
= PrefixCon [arg] -- C p1 p2 p3
| RecCon rec -- C { x = p1, y = p2 }
| InfixCon arg arg -- p1 `C` p2
There's something peculiar about this representation, however. Notice that the comments for the ConDeclGADT
versionof con_args
mention that it can never be InfixCon
. Indeed, GADT constructors can only be written in prefix or record form, never infix. Nevertheless, the types don't reflect this, and as a consequence, many code paths involving HsConDeclDetails
for GADTs have to awkwardly dance around the fact that InfixCon
is unreachable. These include:
-
Here,
get_args (InfixCon {}) = pprPanic "pprConDecl:GADT" (ppr cons)
-
Here,
InfixCon _ _ -> panic "ConDeclGADT InfixCon"
-
And here:
repConstr (InfixCon {}) (Just _) _ = panic "repConstr: infix GADT constructor should be in a PrefixCon"
The root cause of this awkwardness is that we are using HsConDeclDetails
for both Haskell98 data constructors and GADT constructors, when in reality, the details for the two are different. Accordingly, I propose that we use different data types in the two con_args
fields of ConDecl
. Concretely, I propose that we:
-
Add a third type parameter to
HsConDetails
:data HsConDetails arg rec inf = PrefixCon [arg] -- C p1 p2 p3 | RecCon rec -- C { x = p1, y = p2 } | InfixCon inf inf -- p1 `C` p2
-
Factor out today's
HsConDeclDetails
into two further type synonyms:HsConDeclH98Details
(for Haskell98-style data constructors) andHsConDeclGADTDetails
(for GADT constructors):type HsConDeclDetails pass inf = HsConDetails (HsScaled pass (LBangType pass)) (XRec pass [LConDeclField pass]) inf type HsConDeclH98Details pass = HsConDeclDetails pass (HsScaled pass (LBangType pass)) type HsConDeclGADTDetails pass = HsConDeclDetails pass Void
Note that the two only differ in their treatment of the
inf
type parameter. (If we implement #18389, this would change further, but for now, it's only a small difference.) InHsConDeclH98Details
,inf
is instantiated to beHsScaled pass (LBangType pass)
. InHsConDeclGADTDetails
, however,InfixCon
is unreachable, so that is enforced by instantiatinginf
withVoid
.
This refactor was originally motivated by #18389, where we need to separate HsConDeclH98Details
and HsConDeclGADTDetails
for other reasons. But this is a worthwhile refactoring in its own right, so I decided to make a dedicated issue for this.