packages feed

aihc-parser-1.0.0.2: src/Aihc/Parser/Internal/Type.hs

{-# LANGUAGE OverloadedStrings #-}

module Aihc.Parser.Internal.Type
  ( typeParser,
    typeSignatureParser,
    forallTelescopeParser,
    typeInfixParser,
    typeInfixOperatorParser,
    typeHeadInfixParser,
    typeAppParser,
    buildTypeApp,
    buildInfixType,
    typeAtomParser,
    contextItemsParser,
    thSpliceTypeParser,
    arrowKindParser,
  )
where

import Aihc.Parser.Internal.Common
import {-# SOURCE #-} Aihc.Parser.Internal.Expr (exprParser)
import Aihc.Parser.Lex (LexTokenKind (..), lexTokenKind, lexTokenSpan, lexTokenText)
import Aihc.Parser.Syntax
import Data.Char (isLower)
import Data.Functor (($>))
import Data.Text qualified as T
import Text.Megaparsec ((<|>))
import Text.Megaparsec qualified as MP

-- | Parse a Template Haskell type splice: $typ or $(typ)
thSpliceTypeParser :: TokParser Type
thSpliceTypeParser = withSpanAnn (TAnn . mkAnnotation) $ do
  expectedTok TkTHSplice
  body <- parenSpliceBody <|> bareSpliceBody
  pure (TSplice body)
  where
    parenSpliceBody = withSpanAnn (EAnn . mkAnnotation) $ do
      body <- parens exprParser
      pure (EParen body)
    bareSpliceBody = withSpanAnn (EAnn . mkAnnotation) $ do
      EVar <$> identifierNameParser

-- | Report core plus outer extension forms:
--
-- > type -> btype ['->' type]
typeParser :: TokParser Type
typeParser = label "type" $ forallTypeParser <|> contextOrKindSigTypeParser

-- | Type syntax accepted after expression signatures. This keeps the same
-- report core while allowing outer @forall@ and context forms.
typeSignatureParser :: TokParser Type
typeSignatureParser = label "type" $ forallSignatureTypeParser <|> contextOrFunSignatureTypeParser

-- | Extension form wrapping the report core with a kind annotation.
kindSigTypeParser :: TokParser Type
kindSigTypeParser =
  optionalSuffix
    (expectedTok TkReservedDoubleColon *> typeSignatureParser)
    TKindSig
    typeFunParser

-- | Extension form:
--
-- > forall a_1 ... a_n . type
forallSignatureTypeParser :: TokParser Type
forallSignatureTypeParser = withSpanAnn (TAnn . mkAnnotation) $ do
  telescope <- forallTelescopeParser
  attachForallBody telescope <$> typeSignatureParser

contextOrFunSignatureTypeParser :: TokParser Type
contextOrFunSignatureTypeParser = do
  isContextType <- startsWithContextType
  if isContextType then contextSignatureTypeParser else typeFunSignatureParser

contextSignatureTypeParser :: TokParser Type
contextSignatureTypeParser = do
  constraints <- contextItemsParserWith typeSignatureParser typeSignatureContextAtomParser
  expectedTok TkReservedDoubleArrow
  TContext constraints <$> typeSignatureParser

-- | Report core plus extension infix-type support:
--
-- > type -> btype ['->' type]
typeFunSignatureParser :: TokParser Type
typeFunSignatureParser = do
  lhs <- typeSignatureInfixParser
  mArrow <- MP.optional arrowKindParser
  case mArrow of
    Nothing -> pure lhs
    Just arrowKind -> TFun arrowKind lhs <$> typeSignatureParser

contextOrKindSigTypeParser :: TokParser Type
contextOrKindSigTypeParser = do
  isContextType <- startsWithContextType
  if isContextType then contextTypeParser else kindSigTypeParser

-- | Extension form:
--
-- > forall a_1 ... a_n . type
forallTypeParser :: TokParser Type
forallTypeParser = withSpanAnn (TAnn . mkAnnotation) $ do
  telescope <- forallTelescopeParser
  attachForallBody telescope <$> typeParser

attachForallBody :: ForallTelescope -> Type -> Type
attachForallBody telescope body =
  case body of
    TAnn ann (TKindSig ty kind) -> TAnn ann (TKindSig (TForall telescope ty) kind)
    TKindSig ty kind -> TKindSig (TForall telescope ty) kind
    _ -> TForall telescope body

-- | Extension form:
--
-- > forall -> 'forall' binder_1 ... binder_n ('.' | '->')
forallTelescopeParser :: TokParser ForallTelescope
forallTelescopeParser = do
  expectedTok TkKeywordForall
  binders <- MP.some forallBinderParser
  visibility <-
    (expectedTok (TkVarSym ".") $> ForallInvisible)
      <|> (expectedTok TkReservedRightArrow $> ForallVisible)
  pure (ForallTelescope visibility binders)

-- | Parse a single forall binder: {k} | (k :: *) | k
forallBinderParser :: TokParser TyVarBinder
forallBinderParser =
  withSpan $
    -- Inferred binder: {k} | {k :: Type}
    ( do
        expectedTok TkSpecialLBrace
        ident <- tyVarNameParser
        mKind <- MP.optional (expectedTok TkReservedDoubleColon *> typeParser)
        expectedTok TkSpecialRBrace
        pure (\span' -> TyVarBinder [mkAnnotation span'] ident mKind TyVarBInferred TyVarBVisible)
    )
      <|> ( do
              expectedTok TkSpecialLParen
              ident <- tyVarNameParser
              expectedTok TkReservedDoubleColon
              kind <- typeParser
              expectedTok TkSpecialRParen
              pure (\span' -> TyVarBinder [mkAnnotation span'] ident (Just kind) TyVarBSpecified TyVarBVisible)
          )
      <|> ( do
              ident <- tyVarNameParser
              pure (\span' -> TyVarBinder [mkAnnotation span'] ident Nothing TyVarBSpecified TyVarBVisible)
          )

contextTypeParser :: TokParser Type
contextTypeParser = contextTypeParserWith typeParser

contextItemsParser :: TokParser [Type]
contextItemsParser = contextItemsParserWith typeParser typeAtomParser

contextTypeParserWith :: TokParser Type -> TokParser Type
contextTypeParserWith rhsParser = do
  constraints <- contextItemsParserWith rhsParser typeAtomParser
  expectedTok TkReservedDoubleArrow
  rhs <- rhsParser
  pure $
    case rhs of
      TAnn ann (TKindSig ty kind) -> TAnn ann (TKindSig (TContext constraints ty) kind)
      TKindSig ty kind -> TKindSig (TContext constraints ty) kind
      _ -> TContext constraints rhs

-- | Report core plus extension infix-type support:
--
-- > type -> btype ['->' type]
-- > btype -> [btype] atype
typeFunParser :: TokParser Type
typeFunParser = typeFunParserWith typeSignatureParser

typeFunParserWith :: TokParser Type -> TokParser Type
typeFunParserWith rhsParser = do
  lhs <- typeInfixParser
  mArrow <- MP.optional arrowKindParser
  case mArrow of
    Nothing -> pure lhs
    Just arrowKind -> TFun arrowKind lhs <$> rhsParser

-- | Parse an arrow annotation and consume the @->@ token.
-- Handles @->@ (unrestricted), @⊸@ (linear), @%1 ->@ (linear), and @%m ->@
-- (explicit multiplicity).  The @%@-prefixed forms are only attempted when
-- @LinearTypes@ is enabled.
arrowKindParser :: TokParser ArrowKind
arrowKindParser = do
  linearEnabled <- isExtensionEnabled LinearTypes
  (expectedTok TkReservedRightArrow $> ArrowUnrestricted)
    <|> (if linearEnabled then linearArrowKindParser else MP.empty)

linearArrowKindParser :: TokParser ArrowKind
linearArrowKindParser =
  (expectedTok TkLinearArrow $> ArrowLinear)
    <|> MP.try multiplicityAnnotatedArrowParser

-- | Parse @%<mult> ->@, consuming all three tokens.
-- Only matches when @%@ is a prefix operator (no space between @%@ and the
-- multiplicity expression), which is how GHC distinguishes @a %1 -> b@ (linear)
-- from @a % 1 -> b@ (type operator applied to @a@ and @1@, then unrestricted arrow).
multiplicityAnnotatedArrowParser :: TokParser ArrowKind
multiplicityAnnotatedArrowParser = do
  expectedTok TkPrefixPercent
  mult <- typeAtomParser
  expectedTok TkReservedRightArrow
  pure (multiplicityToArrowKind mult)

multiplicityToArrowKind :: Type -> ArrowKind
multiplicityToArrowKind ty =
  case peelTypeAnn ty of
    TTypeLit (TypeLitInteger 1 _) -> ArrowLinear
    _ -> ArrowExplicit ty

-- | Extension layer over the report's @btype@ chain:
--
-- > btype -> [btype] atype
typeInfixParser :: TokParser Type
typeInfixParser = do
  lhs <- typeAppParser
  rest <- MP.many ((,) <$> typeInfixOperatorParser <*> typeInfixRhsParser)
  pure (foldInfixR buildInfixType lhs rest)

typeInfixRhsParser :: TokParser Type
typeInfixRhsParser = do
  rhs <- typeAppParser
  rejectBareTypeImplicitParam rhs

typeSignatureInfixParser :: TokParser Type
typeSignatureInfixParser = do
  lhs <- typeSignatureAppParser
  rest <- MP.many ((,) <$> typeInfixOperatorParser <*> typeSignatureInfixRhsParser)
  pure (foldInfixR buildInfixType lhs rest)

typeSignatureInfixRhsParser :: TokParser Type
typeSignatureInfixRhsParser = do
  rhs <- typeSignatureAppParser
  rejectBareSignatureImplicitParam rhs

-- | Parse a type head that may contain infix operators but NOT type applications.
-- Used for type family heads like @type family l `And` r@ where the head is
-- an infix type, but we don't want to consume trailing type parameters.
typeHeadInfixParser :: TokParser Type
typeHeadInfixParser = do
  lhs <- typeAtomParser
  foldInfixR buildInfixType lhs <$> typeHeadInfixLoopParser

typeHeadInfixLoopParser :: TokParser [((Name, TypePromotion), Type)]
typeHeadInfixLoopParser = MP.many $ MP.try $ do
  op <- typeInfixOperatorParser
  atom <- typeAtomParser
  pure (op, atom)

buildInfixType :: Type -> ((Name, TypePromotion), Type) -> Type
buildInfixType lhs ((op, promoted), rhs) = TInfix lhs op promoted rhs

typeInfixOperatorParser :: TokParser (Name, TypePromotion)
typeInfixOperatorParser =
  promotedInfixOperatorParser
    <|> backtickTypeOperatorParser
    <|> unpromotedInfixOperatorParser
  where
    unpromotedInfixOperatorParser =
      tokenSatisfy "type infix operator" $ \tok ->
        case lexTokenKind tok of
          TkReservedColon -> Just (qualifyName Nothing (mkUnqualifiedName NameConSym ":"), Unpromoted)
          TkVarSym op
            | op /= "."
                && op /= "!"
                && op /= "'" ->
                Just (qualifyName Nothing (mkUnqualifiedName NameVarSym op), Unpromoted)
          TkConSym op -> Just (qualifyName Nothing (mkUnqualifiedName NameConSym op), Unpromoted)
          TkQVarSym modName op -> Just (mkName (Just modName) NameVarSym op, Unpromoted)
          TkQConSym modName op -> Just (mkName (Just modName) NameConSym op, Unpromoted)
          _ -> Nothing

    backtickTypeOperatorParser = MP.try $ do
      expectedTok TkSpecialBacktick
      op <- typeOperatorIdentifierParser
      expectedTok TkSpecialBacktick
      pure (op, Unpromoted)

    typeOperatorIdentifierParser =
      tokenSatisfy "type operator identifier" $ \tok ->
        case lexTokenKind tok of
          TkVarId name -> Just (qualifyName Nothing (mkUnqualifiedName NameVarId name))
          TkConId name -> Just (qualifyName Nothing (mkUnqualifiedName NameConId name))
          TkQVarId modName name -> Just (mkName (Just modName) NameVarId name)
          TkQConId modName name -> Just (mkName (Just modName) NameConId name)
          _ -> Nothing

    promotedInfixOperatorParser = MP.try $ do
      -- Accept both TkVarSym "'" and TkTHQuoteTick for promoted operators
      expectedTok (TkVarSym "'") <|> expectedTok TkTHQuoteTick
      -- After the quote, accept any symbolic infix operator (e.g., ': for promoted cons,
      -- or ':$$: for a promoted user-defined type operator)
      tokenSatisfy "promoted type infix operator" $ \tok ->
        case lexTokenKind tok of
          TkReservedColon -> Just (qualifyName Nothing (mkUnqualifiedName NameConSym ":"), Promoted)
          TkVarSym sym
            | sym /= "." && sym /= "!" ->
                Just (qualifyName Nothing (mkUnqualifiedName NameVarSym sym), Promoted)
          TkConSym sym -> Just (qualifyName Nothing (mkUnqualifiedName NameConSym sym), Promoted)
          TkQVarSym modQual sym -> Just (mkName (Just modQual) NameVarSym sym, Promoted)
          TkQConSym modQual sym -> Just (mkName (Just modQual) NameConSym sym, Promoted)
          _ -> Nothing

-- | Report core:
--
-- > btype -> [btype] atype
typeAppParser :: TokParser Type
typeAppParser = do
  first <- typeAtomParser
  rest <- MP.many typeAppArgParser
  pure (foldl applyTypeAppArg first rest)

typeSignatureAppParser :: TokParser Type
typeSignatureAppParser = do
  first <- typeSignatureAtomParser
  rest <- MP.many typeSignatureAppArgParser
  pure (foldl applyTypeAppArg first rest)

buildTypeApp :: Type -> Type -> Type
buildTypeApp = TApp

typeAppArgParser :: TokParser (Either Type Type)
typeAppArgParser =
  (Left <$> MP.try invisibleTypeAppParser)
    <|> (Right <$> (typeAtomParser >>= rejectBareTypeImplicitParam))

invisibleTypeAppParser :: TokParser Type
invisibleTypeAppParser = do
  expectedTok TkTypeApp
  typeAtomParser >>= rejectBareTypeImplicitParam

typeSignatureAppArgParser :: TokParser (Either Type Type)
typeSignatureAppArgParser =
  (Left <$> MP.try invisibleSignatureTypeAppParser)
    <|> (Right <$> (typeSignatureAtomParser >>= rejectBareSignatureImplicitParam))

typeSignatureContextAtomParser :: TokParser Type
typeSignatureContextAtomParser = typeSignatureAtomParser >>= rejectBareSignatureImplicitParam

rejectBareSignatureImplicitParam :: Type -> TokParser Type
rejectBareSignatureImplicitParam = rejectBareTypeImplicitParam

rejectBareTypeImplicitParam :: Type -> TokParser Type
rejectBareTypeImplicitParam ty =
  case peelTypeAnn ty of
    TImplicitParam {} -> fail "implicit parameter type must be parenthesized"
    _ -> pure ty

invisibleSignatureTypeAppParser :: TokParser Type
invisibleSignatureTypeAppParser = do
  expectedTok TkTypeApp
  typeSignatureAtomParser >>= rejectBareSignatureImplicitParam

applyTypeAppArg :: Type -> Either Type Type -> Type
applyTypeAppArg fn (Left ty) = TTypeApp fn ty
applyTypeAppArg fn (Right ty) = TApp fn ty

-- | Report core:
--
-- > atype -> gtycon
-- >       | tyvar
-- >       | '(' type_1 ',' ... ',' type_k ')'
-- >       | '[' type ']'
-- >       | '(' type ')'
--
-- This parser also admits extension atoms such as promoted types,
-- quasi-quotes, splices, wildcards, and implicit-parameter types.
typeAtomParser :: TokParser Type
typeAtomParser = do
  thAny <- thAnyEnabled
  ipEnabled <- isExtensionEnabled ImplicitParams
  MP.try promotedTypeParser
    <|> typeLiteralTypeParser
    <|> typeQuasiQuoteParser
    <|> (if thAny then thSpliceTypeParser else MP.empty)
    <|> (if ipEnabled then typeImplicitParamParser else MP.empty)
    <|> typeListParser
    <|> MP.try typeParenOperatorParser
    <|> typeParenOrTupleParser
    <|> typeStarParser
    <|> typeWildcardParser
    <|> typeIdentifierParser

typeSignatureAtomParser :: TokParser Type
typeSignatureAtomParser = do
  thAny <- thAnyEnabled
  ipEnabled <- isExtensionEnabled ImplicitParams
  MP.try promotedTypeParser
    <|> typeLiteralTypeParser
    <|> typeQuasiQuoteParser
    <|> (if thAny then thSpliceTypeParser else MP.empty)
    <|> (if ipEnabled then typeImplicitParamParser else MP.empty)
    <|> typeListParser
    <|> MP.try typeParenOperatorParser
    <|> typeParenOrTupleParser
    <|> typeStarParser
    <|> typeWildcardParser
    <|> typeIdentifierParser

-- | Parse an implicit parameter type.
--
-- The body uses 'typeSignatureParser' so @?x :: (T :: K)@ is accepted
-- through the parenthesized atom grammar, but @?x :: T :: K@ is left for an
-- outer kind-signature parser in a plain type context and rejected in a
-- signature context.
typeImplicitParamParser :: TokParser Type
typeImplicitParamParser = withSpanAnn (TAnn . mkAnnotation) $ do
  name <- implicitParamNameParser
  expectedTok TkReservedDoubleColon
  TImplicitParam name <$> typeSignatureParser

typeWildcardParser :: TokParser Type
typeWildcardParser = withSpanAnn (TAnn . mkAnnotation) $ do
  expectedTok TkKeywordUnderscore
  pure TWildcard

typeLiteralTypeParser :: TokParser Type
typeLiteralTypeParser = withSpanAnn (TAnn . mkAnnotation) $ do
  lit <- tokenSatisfy "type literal" $ \tok ->
    case lexTokenKind tok of
      TkInteger n _ -> Just (TypeLitInteger n (lexTokenText tok))
      TkString s -> Just (TypeLitSymbol s (lexTokenText tok))
      TkChar c -> Just (TypeLitChar c (lexTokenText tok))
      _ -> Nothing
  pure (TTypeLit lit)

promotedTypeParser :: TokParser Type
promotedTypeParser = withSpanAnn (TAnn . mkAnnotation) $ do
  -- Accept both TkVarSym "'" and TkTHQuoteTick for promoted types
  -- This handles ambiguity between TH value quotes and promoted types
  expectedTok (TkVarSym "'") <|> expectedTok TkTHQuoteTick
  ty <- typeAtomParser
  maybe (fail "promoted type") pure (markTypePromoted ty)

typeParenOperatorParser :: TokParser Type
typeParenOperatorParser = withSpanAnn (TAnn . mkAnnotation) $ do
  expectedTok TkSpecialLParen
  starIsType <- isExtensionEnabled StarIsType
  unicodeSyntax <- isExtensionEnabled UnicodeSyntax
  op <- tokenSatisfy "type operator" $ \tok ->
    case lexTokenKind tok of
      TkVarSym sym | not (isStarTypeSymbol starIsType unicodeSyntax sym) -> Just (qualifyName Nothing (mkUnqualifiedName NameVarSym sym))
      TkConSym sym | not (isStarTypeSymbol starIsType unicodeSyntax sym) -> Just (qualifyName Nothing (mkUnqualifiedName NameConSym sym))
      TkQVarSym modQual sym -> Just (mkName (Just modQual) NameVarSym sym)
      TkQConSym modQual sym -> Just (mkName (Just modQual) NameConSym sym)
      -- Handle reserved operators that can be used as type constructors
      TkReservedRightArrow -> Just (qualifyName Nothing (mkUnqualifiedName NameVarSym "->"))
      TkReservedColon -> Just (qualifyName Nothing (mkUnqualifiedName NameConSym ":"))
      -- Note: ~ is now lexed as TkVarSym "~" so TkVarSym case handles it
      _ -> Nothing
  expectedTok TkSpecialRParen
  pure $
    case (nameQualifier op, nameType op, nameText op) of
      (Nothing, NameVarSym, "->") -> TBuiltinCon TBuiltinArrow
      (Nothing, NameConSym, ":") -> TBuiltinCon TBuiltinCons
      _ -> TCon op Unpromoted

typeQuasiQuoteParser :: TokParser Type
typeQuasiQuoteParser =
  tokenSatisfy "type quasi quote" $ \tok ->
    case lexTokenKind tok of
      TkQuasiQuote quoter body -> Just (typeAnnSpan (lexTokenSpan tok) (TQuasiQuote quoter body))
      _ -> Nothing

typeIdentifierParser :: TokParser Type
typeIdentifierParser = withSpanAnn (TAnn . mkAnnotation) $ do
  name <- identifierNameParser
  pure $
    case (nameQualifier name, nameType name, T.uncons (nameText name)) of
      (Nothing, NameVarId, Just (c, _))
        | isLower c || c == '_' ->
            TVar (mkUnqualifiedName NameVarId (nameText name))
      _ -> TCon name Unpromoted

typeStarParser :: TokParser Type
typeStarParser = withSpanAnn (TAnn . mkAnnotation) $ do
  starIsType <- isExtensionEnabled StarIsType
  unicodeSyntax <- isExtensionEnabled UnicodeSyntax
  if starIsType
    then do
      spelling <- tokenSatisfy "star kind" $ \tok ->
        case lexTokenKind tok of
          TkVarSym sym | isStarTypeSymbol starIsType unicodeSyntax sym -> Just (lexTokenText tok)
          TkConSym sym | isStarTypeSymbol starIsType unicodeSyntax sym -> Just (lexTokenText tok)
          _ -> Nothing
      pure (TStar spelling)
    else MP.empty

isStarTypeSymbol :: Bool -> Bool -> T.Text -> Bool
isStarTypeSymbol starIsType unicodeSyntax sym =
  starIsType && (sym == "*" || (unicodeSyntax && sym == "★"))

typeListParser :: TokParser Type
typeListParser = withSpanAnn (TAnn . mkAnnotation) $ do
  expectedTok TkSpecialLBracket
  mClosed <- MP.optional (expectedTok TkSpecialRBracket)
  case mClosed of
    Just () -> pure (TBuiltinCon TBuiltinList)
    Nothing -> do
      elems <- typeParser `MP.sepBy1` expectedTok TkSpecialComma
      expectedTok TkSpecialRBracket
      pure (TList Unpromoted elems)

typeParenOrTupleParser :: TokParser Type
typeParenOrTupleParser = withSpanAnn (TAnn . mkAnnotation) $ do
  (tupleFlavor, closeTok) <- tupleDelimsParser
  mClosed <- MP.optional (expectedTok closeTok)
  case mClosed of
    Just () -> pure (TTuple tupleFlavor Unpromoted [])
    Nothing -> do
      MP.try (tupleConstructorParser tupleFlavor closeTok) <|> parenthesizedTypeOrTupleParser tupleFlavor closeTok
  where
    tupleConstructorParser tupleFlavor closeTok = do
      _ <- expectedTok TkSpecialComma
      moreCommas <- MP.many (expectedTok TkSpecialComma)
      expectedTok closeTok
      let arity = 2 + length moreCommas
      case tupleFlavor of
        Boxed -> pure (TBuiltinCon (TBuiltinTuple arity))
        Unboxed -> do
          let tupleConName = "(#" <> T.replicate (arity - 1) "," <> "#)"
          pure (TCon (qualifyName Nothing (mkUnqualifiedName NameConId tupleConName)) Unpromoted)

    parenthesizedTypeOrTupleParser tupleFlavor closeTok = do
      first <- typeParser
      mKind <- if tupleFlavor == Boxed then MP.optional (expectedTok TkReservedDoubleColon *> typeParser) else pure Nothing
      case mKind of
        Just kind -> do
          expectedTok closeTok
          pure (TParen (TKindSig first kind))
        Nothing -> do
          mComma <- MP.optional (expectedTok TkSpecialComma)
          case mComma of
            Nothing -> do
              -- Check for pipe (unboxed sum type)
              mPipe <- if tupleFlavor == Unboxed then MP.optional (expectedTok TkReservedPipe) else pure Nothing
              case mPipe of
                Just () -> do
                  -- (# Type1 | Type2 | ... #) - unboxed sum type
                  rest <- typeParser `MP.sepBy1` expectedTok TkReservedPipe
                  expectedTok closeTok
                  pure (TUnboxedSum (first : rest))
                Nothing -> do
                  expectedTok closeTok
                  case tupleFlavor of
                    Boxed -> pure (TParen first)
                    Unboxed -> pure (TTuple Unboxed Unpromoted [first])
            Just () -> do
              second <- typeParser
              more <- MP.many (expectedTok TkSpecialComma *> typeParser)
              expectedTok closeTok
              pure (TTuple tupleFlavor Unpromoted (first : second : more))

markTypePromoted :: Type -> Maybe Type
markTypePromoted ty =
  case ty of
    TAnn ann sub
      | Just inner <- markTypePromoted sub ->
          Just (TAnn ann inner)
    TCon name _ -> Just (TCon name Promoted)
    TBuiltinCon con -> Just (promoteBuiltinCon con)
    TList _ elems -> Just (TList Promoted elems)
    TTuple tupleFlavor _ elems -> Just (TTuple tupleFlavor Promoted elems)
    TTypeApp fn arg -> TTypeApp <$> markTypePromoted fn <*> pure arg
    _ -> Nothing

promoteBuiltinCon :: TypeBuiltinCon -> Type
promoteBuiltinCon con =
  TCon
    ( qualifyName Nothing $
        case con of
          TBuiltinTuple arity -> mkUnqualifiedName NameConId ("(" <> T.replicate (max 0 (arity - 1)) "," <> ")")
          TBuiltinArrow -> mkUnqualifiedName NameVarSym "->"
          TBuiltinList -> mkUnqualifiedName NameConId "[]"
          TBuiltinCons -> mkUnqualifiedName NameConSym ":"
    )
    Promoted