packages feed

demangler-1.3.2.0: src/Demangler/PPrint.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-- OPTIONS_GHC -ddump-splices #-}

module Demangler.PPrint () where

import           Control.Applicative
import           Data.Char
import           Data.List.NonEmpty ( NonEmpty((:|)) )
import qualified Data.List.NonEmpty as NEL
import           Data.Text ( Text )
import qualified Data.Text as T
import           Text.Sayable

import           Demangler.Context
import           Demangler.Engine
import           Demangler.Structure


#ifdef MIN_VERSION_panic
import           Panic

-- Debug function to cause a Panic with -fdebug builds, or return a placeholder
-- in non-debug mode.  This is usually used for unfinished portions of the
-- output, to provide a useful panic when in development mode but to avoid
-- crashing in normal mode.  Note that the demangling process uses a similar
-- function to fail the parse; here, the parse has completed and we are simply
-- generating output, so we don't have the option to "revert" to the original.
-- Instead, emitting invalid output (without failing) is the most useful
-- operation, since the valid form of that output is not currently
-- known/implemented.
cannotSay :: PanicComponent a => a -> String -> [String] -> b
cannotSay = panic
#else
cannotSay :: a -> String -> [String] -> Saying saytag
cannotSay _ _ rsn = t'"OUTFMT?:{ " &* rsn &- '}'
#endif


data PrefixUQN = PUC Prefix UnqualifiedName
data PrefixCDtor = PCDC Prefix CtorDtor

$(return [])

ctxLst :: forall saytag t a b .
          Sayable saytag (WithContext a)
       => Functor t
       => Foldable t
       => t a -> WithContext b -> Saying saytag
ctxLst l wc = t'"" &+* wCtx l wc

ctxLst' :: Sayable saytag (WithContext a)
        => Functor t
        => Foldable t
        => t a -> WithContext b -> Text -> Saying saytag
ctxLst' l wc sep = sep &:* wCtx l wc

wCtx :: Functor t => t a -> WithContext b -> t (WithContext a)
wCtx a wc = withContext wc <$> a


----------------------------------------------------------------------
-- Sayable instances for printing demangled results

instance {-# OVERLAPPING #-}
  ( Sayable "diagnostic" (WithContext Encoding)
  ) => Sayable "diagnostic" Result where
  sayable = \case
    (Original i, c) -> contextStr (addContext () c) i &- t'"{orig}"
    (Encoded e, c) -> sayable @"diagnostic" $ addContext e c
    (VendorExtended d i, c) ->
      let (s1,s2) = T.span isAlphaNum $ contextStr (addContext () c) i
      in addContext d c &- t'"[clone" &- s1 &+ ']' &+ s2

instance {-# OVERLAPPABLE #-}
  ( Sayable saytag (WithContext Encoding)
  ) => Sayable saytag Result where
  sayable = \case
    (Original i, c) -> sayable @saytag $ contextStr (addContext () c) i
    (Encoded e, c) -> sayable @saytag $ addContext e c
    (VendorExtended d i, c) ->
      let (s1,s2) = T.span isAlphaNum $ contextStr (addContext () c) i
      in addContext d c &- t'"[clone" &- '.' &+ s1 &+ ']' &+ s2

instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''Encoding
  ) => Sayable saytag (WithContext Encoding) where
  sayable wc =
    case contextData wc of
      -- Note: if the function has only a single void argument, print "()"
      -- instead of "(void)"; these are semantically the same, but demangling
      -- emits the former.
      --
      -- Another tricky part is that the FunctionName may contain qualifiers
      -- (esp. "const") but for a function these must be placed at the end,
      -- following the arguments.
      --
      -- Additionally, if this function is being printed as part of a template
      -- argument, then do not print the arguments.  This conforms to GCC c++filt
      -- output, although llvm-cxxfilt *does* print the arguments, however, the
      -- testing oracle is c++filt.
      EncFunc f rty (BaseType Void :| []) -> sayFunction wc f rty []
      EncFunc f rty t -> sayFunction wc f rty $ NEL.toList t
      -- n.b. static functions don't have any visible difference in demangled
      -- form.
      EncStaticFunc f rty (BaseType Void :| []) -> sayFunction wc f rty []
      EncStaticFunc f rty t -> sayFunction wc f rty $ NEL.toList t
      EncConstStructData nm -> sayable @saytag $ withContext wc nm
      EncData nm -> sayable @saytag $ withContext wc nm
      EncSpecial sn -> sayable @saytag $ withContext wc sn

sayFunction :: Sayable saytag (WithContext Type_)
            => WithContext a -> FunctionName -> Maybe Type_ -> [Type_] -> Saying saytag
sayFunction wc fn mbRet args =
  let (nm,q) = cleanFunctionName fn
      part1 = case mbRet of
                Nothing -> withContext wc nm &+ t'""
                Just rty -> withContext wc rty &- withContext wc nm
      part2 = if isTemplateArgContext wc
              then part1
              else part1 &+ '(' &+ ctxLst args wc &+ ')'
  in if null q then part2 else part2 &- ctxLst' q wc " "

instance Sayable saytag (WithContext a)
  => Sayable saytag (NonEmpty (WithContext a)) where
  sayable l = t'"" &+* l

instance {-# OVERLAPPABLE #-} Sayable saytag (WithContext a)
  => Sayable saytag (WithContext (NonEmpty a)) where
  sayable wc = ctxLst (contextData wc) wc

cleanFunctionName :: FunctionName -> (Name, [CVQualifier])
cleanFunctionName (FunctionName nm) =
  case nm of
    NameNested (NestedName p u cvq mbrq) ->
      (NameNested $ NestedName p u [] mbrq, cvq)
    NameNested (NestedTemplateName tp ta cvq mbrq) ->
      (NameNested $ NestedTemplateName tp ta [] mbrq, cvq)
    _ -> (nm, [])

instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''SpecialName
  ) => Sayable saytag (WithContext SpecialName) where
  sayable wc =
    case contextData wc of
      VirtualTable ty -> t'"vtable for" &- withContext wc ty
      TemplateParameterObj ta -> t'"template parameter object for" &- withContext wc ta
      VTT ty -> t'"VTT for" &- withContext wc ty
      TypeInfo ty -> t'"typeinfo for" &- withContext wc ty
      TypeInfoName ty -> t'"typeinfo name for" &- withContext wc ty
      CtorVTable _ -> t'"construction vtable for" &- t'"()"
      Thunk (VirtualOffset _o1 _o2) enc -> t'"virtual thunk to" &- withContext wc enc
      Thunk (NonVirtualOffset _o1) enc -> t'"non-virtual thunk to" &- withContext wc enc


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''FunctionName
  ) => Sayable saytag (WithContext FunctionName) where
  sayable wc = let FunctionName n = contextData wc
               in sayable @saytag $ withContext wc n


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''Name
  ) => Sayable saytag (WithContext Name) where
  sayable wc =
    case contextData wc of
      NameNested nn -> sayable @saytag $ withContext wc nn
      UnscopedName usn -> sayable @saytag $ withContext wc usn
      UnscopedTemplateName nn ta -> withContext wc nn &+ withContext wc ta
      LocalName fs fe _discr -> withContext wc fs &+ t'"::" &+ withContext wc fe -- Discriminators are invisible in demangled form
      StringLitName fs _discr -> sayable @saytag $ withContext wc fs  -- Discriminators are invisible in demangled form


-- Note: this should never actually be used, but the sayableConstraints template
-- haskell production doesn't know that.
instance Sayable saytag Discriminator where
  sayable _ = sayable @saytag $ t'""
instance Sayable saytag (WithContext Discriminator) where
  sayable _ = sayable @saytag $ t'""

instance {--# OVERLAPPABLE #-}
  $(sayableConstraints ''UnscopedName
   ) =>  Sayable saytag (WithContext UnscopedName) where
  sayable wc =
    case contextData wc of
      UnScName False uqn -> sayable @saytag $ withContext wc uqn
      UnScName True uqn -> t'"std::" &+ withContext wc uqn
      UnScSubst subs -> sayable @saytag $ withContext wc subs


instance {-# OVERLAPPABLE #-} Sayable saytag (WithContext Coord) where
  sayable wc = sayable @saytag $ contextStr wc $ contextData wc


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''UnqualifiedName
  ) =>  Sayable saytag (WithContext UnqualifiedName) where
  sayable wc =
    case contextData wc of
      SourceName i [] -> sayable @saytag $ withContext wc i
      SourceName i tags -> withContext wc i &+ ctxLst' tags wc ""
      OperatorName op [] -> t'"operator" &+ withContext wc op
      OperatorName op tags -> t'"operator" &+ withContext wc op &+ ctxLst' tags wc ""
      CtorDtorName cd -> sayable @saytag $ withContext wc cd
      StdSubst subs -> sayable @saytag $ withContext wc subs
      ModuleNamed mn uqn -> ctxLst' mn wc "" &+ withContext wc uqn
      -- GCC c++filt style:
      UnnamedTypeName Nothing -> t'"{unnamed type#1" &+ '}'
      UnnamedTypeName (Just nt) -> t'"{unnamed type#" &+ nt + 2 &+ '}'
      -- llvm-cxxfilt style:
      -- UnnamedTypeName Nothing -> t'"'unnamed" &+ '\''
      -- UnnamedTypeName (Just nt) -> t'"'unnamed" &+ nt &+ '\''


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''SourceName
   ) => Sayable saytag (WithContext SourceName) where
  sayable wc = let SrcName i = contextData wc in sayable @saytag $ contextStr wc i


instance {-# OVERLAPPABLE #-}
  ($(sayableConstraints ''PrefixUQN)
  , Sayable saytag (WithContext PrefixCDtor)
  ) =>  Sayable saytag (WithContext PrefixUQN) where
  sayable wc =
    let PUC p n = contextData wc in
    case n of
      CtorDtorName cd -> sayable @saytag $ withContext wc (PCDC p cd)
      _ -> sayable @saytag $ withContext wc n

instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''ModuleName
  ) => Sayable saytag (WithContext ModuleName) where
  sayable wc =
    let ModuleName isP sn = contextData wc
    in if isP then withContext wc sn &+ ':' else withContext wc sn &+ '.'

{- | Use Sayable (Prefix, CtorDtor, Context) instead, since CtorDtor needs to
   reproduce Prefix name. -}
instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''CtorDtor
   ) =>  Sayable saytag (WithContext CtorDtor) where
  sayable wc =
    case contextData wc of
      CompleteCtor -> '(' &+ ')'
      BaseCtor -> '(' &+ ')'
      CompleteAllocatingCtor -> '(' &+ ')'
      CompleteInheritingCtor t -> withContext wc t &+ '(' &+ ')' -- ?
      BaseInheritingCtor t -> withContext wc t &+ '(' &+ ')' -- ?
      DeletingDtor -> '~' &+ '(' &+ ')'
      CompleteDtor -> '~' &+ '(' &+ ')'
      BaseDtor -> '~' &+ '(' &+ ')'

instance {-# OVERLAPPABLE #-}
  ($(sayableConstraints ''PrefixCDtor)
  ) =>  Sayable saytag (WithContext PrefixCDtor) where
  sayable wc =
    let PCDC p n = contextData wc
        mb'ln = case p of
                  Prefix pfxr -> pfxrLastUQName pfxr
                  _ -> cannot Demangler "sayable"
                       [ "CTORDTOR UNK PFX: " <> show p ]
        pfxrLastUQName = \case
          PrefixUQName (UnnamedTypeName _) PrefixEnd -> Nothing
          PrefixUQName (UnnamedTypeName _) (PrefixTemplateArgs _ PrefixEnd) -> Nothing
          PrefixUQName unm PrefixEnd -> Just unm
          PrefixUQName unm (PrefixTemplateArgs _ PrefixEnd) -> Just unm
          PrefixUQName unm sp -> pfxrLastUQName sp <|> Just unm  -- [note UTC]
          PrefixTemplateArgs _ sp -> pfxrLastUQName sp
          PrefixEnd -> Nothing
    in case mb'ln of
         Just ln ->
           case n of
             CompleteCtor -> sayable @saytag $ withContext wc ln
             BaseCtor -> sayable @saytag $ withContext wc ln
             CompleteAllocatingCtor -> sayable @saytag $ withContext wc ln
             CompleteInheritingCtor t -> sayable @saytag $ withContext wc t -- ??
             BaseInheritingCtor t -> sayable @saytag $ withContext wc t -- ??
             DeletingDtor -> '~' &+ withContext wc ln
             CompleteDtor -> '~' &+ withContext wc ln
             BaseDtor -> '~' &+ withContext wc ln
         Nothing -> sayable @saytag $ withContext wc n

-- [Note UTC:] When printing a constructor or destructor that includes an
-- UnnamedTypeName, there are differences between GCC's c++filt and LLVM's
-- llvm-cxxfilt.  We adopt the former because the demangle tests use c++filt as
-- an oracle, but it is possible to switch to the LLVM style (at compile time) by
-- removing the alternative return of Just unm from the indicated
--
-- Example:
--    _ZN3FooUt3_C2Ev is the base constructor for the 5th unnamed type
--    in the Foo namespace.
--
--      c++filt _ZN3FooUt3_C2Ev -------> Foo::{unnamed type#5}::Foo()
--      llvm-cxxfilt _ZN3FooUt3_C2Ev --> Foo::'unnamed3'::()'


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''Operator
  ) =>  Sayable saytag (WithContext Operator) where
  sayable wc =
    let op = contextData wc in
    case lookup op opTable of
      Just (_, (_, o)) -> sayable @saytag o
      Nothing ->
        -- TODO: if these are printed as part of an expression rather than an
        -- UnqualifiedName, the prefix space will be wrong (the latter prints
        -- "operator" to name the function whereas the former just prints the
        -- operation).  If this is an issue, probably needs to be a flag in
        -- WithContext to indicate if this is an expression or not.
        case op of
          OpCast ty -> ' ' &+ withContext wc ty
          OpString snm -> ' ' &+ withContext wc snm
          OpVendor n snm -> t'"vendor" &- n &- withContext wc snm  -- ?
          _ -> cannotSay Demangler "sayable"
               [ "Operator not in opTable or with a specific override:"
               , show op
               ]

instance {-# OVERLAPPABLE #-}
  ($(sayableConstraints ''NestedName)
  , Sayable saytag (WithContext PrefixCDtor)
  ) => Sayable saytag (WithContext NestedName) where
  sayable wc =
    let qualrefs q r = ctxLst' q wc " " &? wCtx r wc
    in case contextData wc of
      NestedName p (CtorDtorName nm) q r ->
        qualrefs q r &+ withContext wc p &+ t'"::" &+ withContext wc (PCDC p nm)
      NestedName EmptyPrefix nm q r -> qualrefs q r &+ withContext wc nm
      NestedName p nm q r -> qualrefs q r &+ withContext wc p &+ t'"::" &+ withContext wc nm
      NestedTemplateName tp ta q r -> qualrefs q r &+ withContext wc tp &+ withContext wc ta


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''Prefix
  ) => Sayable saytag (WithContext Prefix) where
  sayable wc =
    case contextData wc of
      PrefixTemplateParam tp prefixr -> withContext wc tp &+ withContext wc prefixr
      PrefixDeclType dt prefixr -> withContext wc dt &+ withContext wc prefixr
      PrefixClosure cp -> sayable @saytag $ withContext wc cp -- ??
      Prefix prefixr -> sayable @saytag $ withContext wc prefixr


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''PrefixR
  ) => Sayable saytag (WithContext PrefixR) where
  sayable wc =
    case contextData wc of
      PrefixUQName uqn pfr@(PrefixUQName {}) -> withContext wc uqn &+ t'"::" &+ withContext wc pfr
      PrefixUQName uqn pfr -> withContext wc uqn &+ withContext wc pfr
      PrefixTemplateArgs ta pfr -> withContext wc ta &+ withContext wc pfr
      PrefixEnd -> sayable @saytag $ t'""


instance {-# OVERLAPPABLE #-} Sayable saytag (WithContext CVQualifier) where
  sayable wc =
    case contextData wc of
      Restrict -> sayable @saytag $ t'"restrict"
      Volatile -> sayable @saytag $ t'"volatile"
      Const_ -> sayable @saytag $ t'"const"

instance {-# OVERLAPPABLE #-} Sayable saytag (WithContext RefQualifier) where
  sayable wc =
    case contextData wc of
      Ref -> sayable @saytag '&'
      RefRef -> sayable @saytag $ t'"&&"

instance {-# OVERLAPPABLE #-}
  ($(sayableConstraints ''TemplatePrefix)
  , Sayable saytag (WithContext PrefixUQN)
  ) => Sayable saytag (WithContext TemplatePrefix) where
  sayable wc =
    case contextData wc of
      GlobalTemplate uqns -> ctxLst' uqns wc "::"
      NestedTemplate pr uqns -> withContext wc pr &+ t'"::"
                                &+ ctxLst' (PUC pr <$> uqns) wc "::"
      TemplateTemplateParam tp -> sayable @saytag $ withContext wc tp


instance {-# OVERLAPPABLE #-}
  (Sayable saytag (WithContext TemplateArg)
  ) => Sayable saytag (WithContext TemplateArgs) where
  sayable wc = let args = contextData wc
                   args' = filter (not . emptyArgPack) $ NEL.toList args
                   emptyArgPack (TArgPack []) = True
                   emptyArgPack _ = False
               in '<' &+ ctxLst args' wc &+ templateArgsEnd args

-- C++ requires a space between template argument closures to resolve the parsing
-- ambiguity between that and a right shift operation.(e.g. "list<foo<int> >"
-- instead of "list<foo<int>>"
templateArgsEnd :: TemplateArgs -> String
templateArgsEnd args = case NEL.last args of
                        TArgPack targs ->
                          case NEL.nonEmpty targs of
                            Just args' -> templateArgsEnd args'
                            -- Expected to need ellipsis here, but c++filt does
                            -- not emit them.
                            -- Nothing -> "..."
                            Nothing -> ">"
                        TArgType (ClassUnionStructEnum
                                  (NameNested
                                   (NestedTemplateName {}))) -> " >"
                        TArgType (ClassUnionStructEnum
                                  (UnscopedTemplateName {})) -> " >"
                        _ -> ">"

instance {-# OVERLAPPABLE #-}
  ($(sayableConstraints ''TemplateArg)
  ) => Sayable saytag (WithContext TemplateArg) where
  sayable wc =
    case contextData wc of
      TArgType ty -> sayable @saytag $ withContextForTemplateArg wc ty
      TArgSimpleExpr ep -> sayable @saytag $ withContextForTemplateArg wc ep
      TArgExpr expr -> sayable @saytag $ withContextForTemplateArg wc expr
      TArgPack tas ->
        -- Expected some ellipses (see
        -- https://en.cppreference.com/w/cpp/language/parameter-pack), but
        -- c++filt does not show them in that manner.
        --
        -- if null tas  then '.' &+ ".."
        -- else (NEL.fromList tas, c) &+ "..."
        --
        -- Do not simply defer to the TemplateArgs sayable because that will
        -- engender another pair of surrounding angle brackets.
        ctxLst tas $ withContextForTemplateArg wc ()

instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''Expression
  ) => Sayable saytag (WithContext Expression) where
  sayable wc =
    case contextData wc of
      ExprUnary op expr -> withContext wc op &+ withContext wc expr
      ExprBinary op expr1 expr2 -> withContext wc expr1 &+ withContext wc op &+ withContext wc expr2
      ExprTrinary op expr1 expr2 expr3 ->
        '(' &+ withContext wc expr1 &+ ')' &+ withContext wc op &+ withContext wc expr2 &- ':' &- withContext wc expr3
      ExprPfxPlus expr -> t'"++" &+ withContext wc expr
      ExprPfxMinus expr -> t'"--" &+ withContext wc expr
      ExprCall (exprc :| args) -> withContext wc exprc &+ '(' &+ ctxLst args wc &+ ')'
      ExprConvert1 ty expr -> '(' &+ withContext wc ty &+ ')' &+ withContext wc expr
      ExprConvertSome ty exprs -> '(' &+ withContext wc ty &+ t'")(" &+ ctxLst exprs wc &+ ')'
      ExprConvertInit ty brexprs -> withContext wc ty &+ '{' &+ ctxLst brexprs wc &+ '}'
      ExprBracedInit brexprs -> '{' &+ ctxLst brexprs wc &+ '}'
      ExprNew gs exprs ty -> t'"new (" &+ ctxLst exprs wc &+ ')'
                             &- (if gs then "::" else t'"") &+ withContext wc ty
      ExprNewInit gs exprs ty i -> t'"new (" &+ ctxLst exprs wc &+ ')'
                                   &- (if gs then "::" else t'"") &+ withContext wc ty
                                   &- '(' &+ withContext wc i &+ ')'
      ExprNewArray gs exprs ty -> t'"new[] (" &+ ctxLst exprs wc &+ ')'
                                  &- (if gs then "::" else t'"") &+ withContext wc ty
      ExprNewInitArray gs exprs ty i -> t'"new[] (" &+ ctxLst exprs wc &+ ')'
                                        &- (if gs then "::" else t'"") &+ withContext wc ty
                                        &- '(' &+ withContext wc i &+ ')'
      ExprDel gs expr -> t'"delete" &- (if gs then "::" else t'"") -- ??
                         &+ withContext wc expr
      ExprDelArray gs expr -> t'"delete[]" &- (if gs then "::" else t'"") -- ??
                              &+ withContext wc expr
      ExprDynamicCast ty expr -> t'"dynamic_cast<" &+ withContext wc ty &+ t'">("
                                 &+ withContext wc expr &+ ')'
      ExprStaticCast ty expr -> t'"static_cast<" &+ withContext wc ty &+ t'">("
                                &+ withContext wc expr &+ ')'
      ExprConstCast ty expr -> t'"const_cast<" &+ withContext wc ty &+ t'">("
                               &+ withContext wc expr &+ ')'
      ExprReinterpretCast ty expr -> t'"reinterpret_cast<" &+ withContext wc ty &+ t'">("
                                     &+ withContext wc expr &+ ')'
      ExprTypeIdType ty -> t'"typeid(" &+ withContext wc ty &+ ')'
      ExprTypeId expr -> t'"typeid(" &+ withContext wc expr &+ ')'
      ExprSizeOfType ty -> t'"sizeof(" &+ withContext wc ty &+ ')'
      ExprSizeOf expr -> t'"sizeof(" &+ withContext wc expr &+ ')'
      ExprAlignOfType ty -> t'"alignof(" &+ withContext wc ty &+ ')'
      ExprAlignOf expr -> t'"alignof(" &+ withContext wc expr &+ ')'
      ExprNoException expr -> t'"noexcept(" &+ withContext wc expr &+ ')'
      ExprTemplateParam tp -> sayable @saytag $ withContext wc tp
      ExprFunctionParam fp -> sayable @saytag $ withContext wc fp
      ExprField expr urn -> withContext wc expr &+ '.' &+ withContext wc urn
      ExprFieldPtr expr urn -> withContext wc expr &+ t'"->" &+ withContext wc urn
      ExprFieldExpr baseExp fieldExp -> withContext wc baseExp &+ t'".*" &+ withContext wc fieldExp
      ExprSizeOfTmplParamPack tp -> t'"sizeof...(" &+ withContext wc tp &+ ')'
      ExprSizeOfFuncParamPack fp -> t'"sizeof...(" &+ withContext wc fp &+ ')'
      ExprSizeOfCapturedTmplParamPack tas -> t'"sizeof...(" &+ ctxLst tas wc &+ ')'
      ExprPack expr -> withContext wc expr &+ t'"..."
      ExprUnaryLeftFold op expr  -> '(' &+ t'"..." &- withContext wc op &- withContext wc expr &+ ')'
      ExprUnaryRightFold op expr -> '(' &+ withContext wc expr &- withContext wc op &- t'"..." &+ ')'
      ExprBinaryLeftFold op exprL exprR  ->
        '(' &+ withContext wc exprL &- withContext wc op &- t'"..." &- withContext wc op &- withContext wc exprR &+ ')'
      ExprBinaryRightFold op exprL exprR ->
        '(' &+ withContext wc exprL &- withContext wc op &- t'"..." &- withContext wc op &- withContext wc exprR &+ ')'
      ExprThrow expr -> t'"throw" &- withContext wc expr
      ExprReThrow -> sayable @saytag $ t'"throw"
      ExprVendorExtended sn tas -> withContext wc sn &+ '<' &+ ctxLst tas wc &+ '>'
      ExprUnresolvedName urn -> sayable @saytag $ withContext wc urn
      ExprPrim pe -> sayable @saytag $ withContext wc pe

instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''ExprPrimary
  ) => Sayable saytag (WithContext ExprPrimary) where
  sayable wc =
    case contextData wc of
      IntLit ty n ->
        -- Normally these are printed with a typecast (e.g. `(type)`) ".
        -- However, C and C++ have some special situations where they can use
        -- special suffixes instead (e.g. `10ul` for unsigned long).  And some
        -- are just wholesale changes.
        case ty of
          BaseType Bool_ -> t'"" &+ if n > 0 then t'"true" else t'"false"
          BaseType bty -> case lookup bty builtinTypeTable of
                            Just (_, _, Just sfx) -> n &+ sfx
                            Just (_, cst, Nothing) -> '(' &+ cst &+ ')' &+ n
                            _ -> '(' &+ withContext wc ty &+ ')' &+ n
          _ -> '(' &+ withContext wc ty &+ ')' &+ n
      FloatLit ty n -> '(' &+ withContext wc ty &+ ')' &+ n
      ComplexFloatLit ty r i -> '(' &+ withContext wc ty &+ ')' &+ '(' &+ r &+ ',' &- i &+ ')'
      DirectLit ty -> '(' &+ withContext wc ty &+ t'")NULL"  -- except String?
      NullPtrTemplateArg ty -> '(' &+ withContext wc ty &+ t'")0"
      ExternalNameLit enc -> sayable @saytag $ withContext wc enc


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''BracedExpression
   ) => Sayable saytag (WithContext BracedExpression) where
  sayable wc =
    case contextData wc of
      BracedExpr e -> sayable @saytag $ withContext wc e
      BracedFieldExpr sn be' -> '.' &+ withContext wc sn &+ withContext wc be'
      BracedIndexExpr ixe be' ->
        '[' &+ withContext wc ixe &+ ']' &+ '=' &+ '(' &+ withContext wc be' &+ ')'
      BracedRangedExpr sr er be' -> '[' &+ withContext wc sr &- t'"..." &- withContext wc er &+ ']'
                                    &- '=' &- withContext wc be'


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''InitializerExpr
   ) => Sayable saytag (WithContext InitializerExpr) where
  sayable wc = let Initializer ies = contextData wc in ctxLst ies wc


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''FunctionParam
   ) => Sayable saytag (WithContext FunctionParam) where
  sayable wc =
    case contextData wc of
      FP_This -> sayable @saytag $ t'"this"
      FP_ cvq n -> sayable @saytag $ '{' &+ ctxLst' cvq wc " " &+ t'"parm#" &+ n &+ '}'


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''UnresolvedName
   ) => Sayable saytag (WithContext UnresolvedName) where
  sayable wc =
    case contextData wc of
      URN_Base True burn -> t'"::" &+ withContext wc burn
      URN_Base False burn -> sayable @saytag $ withContext wc burn
      URNScopedRef urt burn -> withContext wc urt &+ t'"::" &+ withContext wc burn
      URNSubScopedRef urt urqls burn -> withContext wc urt &+ t'"::"
                                        &+ ctxLst' urqls wc (t'"::")
                                        &+ t'"::" &+ withContext wc burn
      URNQualRef True urqls burn -> t'"::"
                                    &+ ctxLst' urqls wc (t'"::")
                                    &+ t'"::" &+ withContext wc burn
      URNQualRef False urqls burn -> ctxLst' urqls wc (t'"::")
                                     &+ t'"::" &+ withContext wc burn


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''BaseUnresolvedName
   ) => Sayable saytag (WithContext BaseUnresolvedName) where
  sayable wc =
    case contextData wc of
      BUName sn Nothing -> sayable @saytag $ withContext wc sn
      BUName sn (Just targs) -> withContext wc sn &+ withContext wc targs
      BUOnOperator op -> sayable @saytag $ withContext wc op
      BUOnOperatorT op targs -> withContext wc op &+ withContext wc targs -- ?
      BUDestructorUnresolvedType urt -> '~' &+ withContext wc urt
      BUDestructorSimpleId sn Nothing -> '~' &+ withContext wc sn
      BUDestructorSimpleId sn (Just targs) -> '~' &+ withContext wc sn &+ withContext wc targs


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''UnresolvedType
   ) => Sayable saytag (WithContext UnresolvedType) where
  sayable wc =
    case contextData wc of
      URTTemplate tp Nothing -> sayable @saytag $ withContext wc tp
      URTTemplate tp (Just targs) -> withContext wc tp &+ withContext wc targs
      URTDeclType dt -> sayable @saytag $ withContext wc dt
      URTSubstPrefix pfx -> sayable @saytag $ withContext wc pfx


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''UnresolvedQualifierLevel
   ) => Sayable saytag (WithContext UnresolvedQualifierLevel) where
  sayable wc =
    case contextData wc of
      URQL sn Nothing -> sayable @saytag $ withContext wc sn
      URQL sn (Just targs) -> withContext wc sn &+ withContext wc targs


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''DeclType
   ) => Sayable saytag (WithContext DeclType) where
  sayable wc =
    case contextData wc of
      DeclType expr -> t'"decltype (" &+ withContext wc expr &+ ')'
      DeclTypeExpr expr -> t'"decltype (" &+ withContext wc expr &+ ')'


instance {-# OVERLAPPABLE #-} Sayable saytag (WithContext ClosurePrefix) where
  sayable _ = cannotSay Demangler "sayable"
              [ "No Sayable for ClosurePrefix" ]

instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''Substitution
  ) => Sayable saytag (WithContext Substitution) where
  sayable wc =
    case contextData wc of
      SubStd -> t'"std" &+ t'""
      SubAlloc -> t'"std" &+ t'"::allocator"
      SubBasicString -> t'"std" &+ t'"::basic_string"
      SubStdType stdTy -> sayable @saytag $ withContext wc stdTy

instance {-# OVERLAPPABLE #-} Sayable saytag (WithContext StdType) where
  sayable wc =
    let ct = t'"std::char_traits<char>" in
    case contextData wc of
      BasicStringChar -> t'"std::basic_string<char," &- ct &+ t'", std::allocator<char> >"
      BasicIStream -> t'"std::basic_istream<char," &- ct &- '>'
      BasicOStream -> t'"std::basic_ostream<char," &- ct &- '>'
      BasicIOStream -> t'"std::basic_iostream<char," &- ct &- '>'


-- n.b. LLVM and GNU syntax seems to be [abi:foo][abi:bar], despite the website
-- documentation of [[gnu::abi_tag ("foo", "bar")]]
instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''ABI_Tag
  ) => Sayable saytag (WithContext ABI_Tag) where
  sayable wc = let ABITag p = contextData wc in t'"[abi:" &+ withContext wc p &+ ']'

instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''Type_
 ) => Sayable saytag (WithContext Type_) where
  sayable wc =
    case contextData wc of
      BaseType t -> sayable @saytag $ withContext wc t
      QualifiedType [] [] t -> sayable @saytag $ withContext wc t
      QualifiedType eqs [] t -> withContext wc t &+ ctxLst' eqs wc " "
      QualifiedType [] cvqs t -> withContext wc t &- ctxLst' cvqs wc " "
      QualifiedType eqs cvqs t -> withContext wc t &- ctxLst' eqs wc " " &- ctxLst' cvqs wc " "
      ClassUnionStructEnum n -> sayable @saytag $ withContext wc n
      ClassStruct n -> sayable @saytag $ withContext wc n
      Union n -> sayable @saytag $ withContext wc n
      Enum n -> sayable @saytag $ withContext wc n
      ty@(Function {}) -> sayFunctionType ty (t'"") wc
      Pointer f@(Function {}) -> sayFunctionType f (t'"(*)") wc
      Pointer (ArrayType bnd t) -> withContext wc t &- t'"(*)" &- '[' &+ withContext wc bnd &+ ']'
      Pointer t -> withContext wc t &+ '*'
      LValRef (ArrayType bnd t) -> withContext wc t &- t'"(&)" &- '[' &+ withContext wc bnd &+ ']'
      LValRef t -> withContext wc t &+ '&'
      RValRef (LValRef t@(QualifiedType _ [Const_] _)) ->
        -- An rvalue may be used to initialize a const lvalue reference; see
        -- https://en.cppreference.com/w/cpp/language/value_category
        sayable @saytag $ withContext wc (LValRef t)
      RValRef t -> withContext wc t &+ t'"&&"
      ComplexPair t -> withContext wc t &- t'"complex"
      Imaginary t -> withContext wc t &- t'"imaginary"
      ArrayType bnd t -> withContext wc t &+ '[' &+ withContext wc bnd &+ ']'
      Template tp ta -> withContext wc tp &- withContext wc ta -- ??
      Cpp11PackExpansion ts ->
        -- XXX expected some "..." (see
        -- https://en.cppreference.com/w/cpp/language/parameter-pack) but c++filt
        -- does not visibly decorate these.
        ctxLst ts wc
      StdType stdTy -> sayable @saytag $ withContext wc stdTy
      DeclType_ dt -> sayable @saytag $ withContext wc dt
      PointerToMember cty mty@(Function {}) ->
        sayFunctionType mty ('(' &+ withContext wc cty &+ t'"::*)") wc
      PointerToMember cty mty -> '(' &+ withContext wc cty &+ t'"::*)" &+ withContext wc mty -- ??


sayFunctionType :: Sayable saytag nm => Type_ -> nm -> WithContext Type_ -> Saying saytag
sayFunctionType (Function cvqs mb'exc trns isExternC rTy argTys mb'ref) nm wc =
  ctxLst' cvqs wc " "
  &? wCtx mb'exc wc
  &+ withContext wc trns
  &+ (if isExternC then t'" extern \"C\"" else t'"")
  &+ withContext wc rTy
  &- nm &+ '('
  &+* (case argTys of
          BaseType Void :| [] -> []
          _ -> wCtx (NEL.toList argTys) wc
      )
  &+ ')'
  &? wCtx mb'ref wc
sayFunctionType _ _ _ = cannotSay Demangler "sayFunctionType"
                        [ "Called with a type that is not a Function!" ]


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''ArrayBound
  ) => Sayable saytag (WithContext ArrayBound) where
  sayable wc =
    case contextData wc of
      NoBounds -> sayable @saytag $ t'""
      NumBound i -> sayable @saytag i
      ExprBound e -> sayable @saytag $ withContext wc e


instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''ExceptionSpec
  ) => Sayable saytag (WithContext ExceptionSpec) where
  sayable wc =
    case contextData wc of
      NonThrowing -> sayable @saytag $ t'"noexcept"
      ComputedThrow expr -> t'"throw" &- withContext wc expr -- ?
      Throwing tys -> t'"throw (" &+ wCtx tys wc &+ ')' -- ?

instance {-# OVERLAPPABLE #-} Sayable saytag (WithContext Transaction) where
  sayable wc =
    case contextData wc of
      TransactionSafe -> sayable @saytag $ t'"safe" -- ?
      TransactionUnsafe -> sayable @saytag $ t'""

instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''BaseType
  ) => Sayable saytag (WithContext BaseType) where
  sayable wc =
    case lookup (contextData wc) builtinTypeTable of
      Just (_,s,_) -> sayable @saytag s
      Nothing ->
        case contextData wc of
          FloatN n -> t'"std::float" &+ n &+ t'"_t"
          FloatNx n -> t'"std::float" &+ n &+ t'"x_t"
          SBitInt n -> t'"signed _BitInt(" &+ n &+ ')'
          UBitInt n -> t'"unsigned _BitInt(" &+ n &+ ')'
          VendorExtendedType nm mb'ta -> withContext wc nm &? wCtx mb'ta wc
          t -> cannotSay Demangler "sayable.Basetype"
               [ "Unknown BaseType not listed in the builtinTypeTable"
               , show t
               ]

instance {-# OVERLAPPABLE #-} Sayable saytag (WithContext CallOffset) where
  sayable _wc =
    cannotSay Demangler "Sayable CallOffset"
    [ "The CallOffset is for a thunk or covariant return thunk"
    , "and is not expected to be printed."
    ]

instance {-# OVERLAPPABLE #-}
  $(sayableConstraints ''SubsCandidate
  ) => Sayable saytag (WithContext SubsCandidate) where
  sayable wc = -- For debug only
    case contextData wc of
      SC_Type t -> t'"SC_Ty" &- withContext wc t
      SC_UQName True n -> t'"SC_UN" &- t'"std::" &+ withContext wc n
      SC_UQName _ n -> t'"SC_UN" &- withContext wc n
      SC_Prefix p -> t'"SC_PR" &- withContext wc p
      SC_TemplatePrefix tp -> t'"SC_TP" &- withContext wc tp
      SC_UnresolvedType urt -> t'"SC_URT" &- withContext wc urt