Allow dynamically bound promoted constructors in UInfixT
Motivation
(See #20773 (comment 395543) for workaround)
I will use a GHCi session as motivating examples. We'll need these extensions and imports:
ghci> :seti -XDataKinds -XTemplateHaskell
ghci> import Language.Haskell.TH
ghci> import Data.Proxy
I will be using a punned constructor:
ghci> data (:::) = * ::: *
As long as we can find the global name of the data constructor, we can use it as a promoted infix constructor.
We do that by writing '(:::)
:
ghci> intT = ConT (mkName "Int")
ghci> :t Proxy @($(pure $ UInfixT intT '(:::) intT))
Proxy @($(pure $ UInfixT intT promoted intT))
:: Proxy (Int '::: Int)
But what if we don't know from where the name will be imported, and want to look it up dynamically?
ghci> Proxy @($(pure $ UInfixT intT (mkName "':::") intT))
<interactive>:53:10: error:
* Illegal type constructor or class name: ':::
When splicing a TH type: (Int '::: Int)
* In the untyped splice:
$(pure $ UInfixT intT (mkName "':::") intT)
Alas, this does not work! And using mkName ":::"
wouldn't work either, since then what we're looking up is the type constructor, not the promoted data constructor.
This is exactly what I need to do in a package I'm writing, hence I'm opening this ticket. A workaround that comes to mind is encoding every infix application in types as a double AppT
, like Template Haskell itself does (#15824), but since we're trying to find the name dynamically, we don't know what its fixity is, so we have to use UInfixT
.
Proposal
Make it possible to dynamically refer to promoted data constructors in UInfixT.
Name
is an abstract datatype defined as
data Name = Name OccName NameFlavour
data NameFlavour
= NameS -- ^ An unqualified name; dynamically bound
| NameQ ModName -- ^ A qualified name; dynamically bound
| NameU !Uniq -- ^ A unique local name
| NameL !Uniq -- ^ Local name bound outside of the TH AST
| NameG NameSpace PkgName ModName -- ^ Global name bound outside of the TH AST:
-- An original name (occurrences only, not binders)
-- Need the namespace too to be sure which
-- thing we are naming
As you can see, the NameSpace
is only specified in the last case. For this feature request, this would likely have to be added to NameS
and NameQ
as well.
An alternative would be to allow a leading '
in a dynamically bound name to indicate promotion, but this seems a bit unprincipled. It would retain greater backwards-compatibility, however (though keep in mind that Name
is documented as being abstract - its constructor is exported from Language.Haskell.TH.Syntax
, but not from Language.Haskell.TH
).
A further alternative would be to change the arguments to InfixT
and UInfixT
, though this is less backwards-compatible than either of the previous options, since these are not abstract.