packages feed

type-reflection-1.0: src/Type/Reflection/Name.hs

{-# LANGUAGE OverloadedStrings #-}

{-|
Description : Utilities for controlling how to serialize type representations into strings
Copyright   : Richard Eisenberg
License     : MIT
Maintainer  : rae@richarde.dev
Stability   : experimental

This module provides three different ways of serializing a 'TypeRep' into a 'Text':
qualifying no names, qualifying all names, or package-qualifying all names. In order
to support meaningful notions of equality on these string representations (for e.g.
using them as keys in a map), @newtype@s are provided for each format.

-}

module Type.Reflection.Name (
  -- * Unqualified names
  Unqualified(..), getUnqualified, showUnqualified,

  -- * Qualified names
  Qualified(..), getQualified, showQualified,

  -- * Package-qualified names
  PackageQualified(..), getPackageQualified, showPackageQualified,

  -- * Class-based access
  TypeText(..), defaultRenderTypeRep,

  ) where

import Data.String ( IsString(..) )
import Data.Hashable ( Hashable )
import Data.Text ( Text )

import Type.Reflection

------------------------------------------------------
-- TypeText, a class that describes how TyCons are rendered

-- | The 'TypeText' class describes how a 'TyCon' is rendered
-- into a textual representation, and it is used to render a 'TypeRep'
-- by 'renderTypeRep'. The 'IsString' superclass is needed to insert
-- connectives like spaces, dots, and parentheses, and the 'Monoid'
-- superclass is needed to stitch these pieces together.
--
-- Only 'renderTyCon' is needed for instances; if 'renderTypeRep' is
-- omitted, then the default rendering, via 'defaultRenderTypeRep' is
-- used. See that function for more details.
--
-- The only consistency law for 'TypeText' is this, holding for all
-- @tr@ of type 'TypeRep' :
--
-- > case tr of Con' tc [] -> renderTypeRep tr == renderTyCon tc
-- >            _ -> True
--
-- In other words, rendering a 'TypeRep' consisting only of a 'TyCon'
-- is the same as rendering the 'TyCon' itself. If the 'TypeRep' is not
-- a plain 'TyCon' (with no kind arguments), then there are no applicable
-- laws. Note that the law uses '==', even though 'Eq' is not a superclass
-- of 'TypeText'.
class (IsString tt, Monoid tt, Ord tt) => TypeText tt where
  renderTyCon :: TyCon -> tt

  renderTypeRep :: TypeRep t -> tt
  renderTypeRep = defaultRenderTypeRep

  {-# MINIMAL renderTyCon #-}

------------------------------------------------------
-- Unqualified

-- | A rendering of a type where no components have module or package qualifications.
-- Data constructors used in types are preceded with a @'@, and infix operator types
-- are printed without parentheses. Uses 'defaultRenderTypeRep' to render types; see
-- that function for further details.
newtype Unqualified = MkUnqualified Text
  deriving (Eq, Ord, Show, Hashable, Semigroup, Monoid, IsString)

instance TypeText Unqualified where
  renderTyCon = fromString . tyConName

-- | Extract the contents of an 'Unqualified' rendering
getUnqualified :: Unqualified -> Text
getUnqualified (MkUnqualified str) = str

-- | Convert a 'TypeRep' into an 'Unqualified' representation
showUnqualified :: TypeRep t -> Unqualified
showUnqualified = renderTypeRep

----------------------------------------------------------
-- Qualified

-- | A rendering of a type where all components have module qualifications.
-- Data constructors used in types are preceded first with their module qualification,
-- and then with a @'@, and infix operator types
-- are printed without parentheses. So we have @Data.Proxy.'Proxy@ for the v'Proxy' constructor.
-- Uses 'defaultRenderTypeRep' to render types; see
-- that function for further details.j
--
-- Note that module qualifications arise from the module that defines a type, even if this
-- is a hidden or internal module. This fact means that internal-structure changes in
-- your libraries may affect how your types are rendered, including changes in e.g. @base@.
newtype Qualified = MkQualified Text
  deriving (Eq, Ord, Show, Hashable, Semigroup, Monoid, IsString)

instance TypeText Qualified where
  renderTyCon tc = fromString (tyConModule tc) <> "." <> fromString (tyConName tc)

-- | Extract the contents of a 'Qualified' rendering
getQualified :: Qualified -> Text
getQualified (MkQualified str) = str

-- | Convert a 'TypeRep' into a 'Qualified' representation
showQualified :: TypeRep t -> Qualified
showQualified = renderTypeRep

------------------------------------------------------------
-- PackageQualified

-- | A rendering of a type where all components have package and module qualifications
-- Data constructors used in types are preceded first with their package qualification,
-- and then their module qualification,
-- and then with a @'@, and infix operator types
-- are printed without parentheses. So we have @base.Data.Proxy.'Proxy@ for the v'Proxy' constructor.
-- Uses 'defaultRenderTypeRep' to render types; see
-- that function for further details.
--
-- Note that module qualifications arise from the module that defines a type, even if this
-- is a hidden or internal module. This fact means that internal-structure changes in
-- your libraries may affect how your types are rendered, including changes in e.g. @base@.
newtype PackageQualified = MkPackageQualified Text
  deriving (Eq, Ord, Show, Hashable, Semigroup, Monoid, IsString)

instance TypeText PackageQualified where
 renderTyCon tc = fromString (tyConPackage tc) <> "." <>
                  fromString (tyConModule tc) <> "." <>
                  fromString (tyConName tc)

-- | Extract the contents of a 'PackageQualified' name
getPackageQualified :: PackageQualified -> Text
getPackageQualified (MkPackageQualified str) = str

-- | Convert a 'TypeRep' into a 'PackageQualified' representation
showPackageQualified :: TypeRep t -> PackageQualified
showPackageQualified = renderTypeRep

----------------------------------------------
-- Worker

-- | Render a 'TypeRep' into an instance of 'TypeText'. This follows the following
-- rules for how to render a type:
--
-- * Function types are rendered infix. If a function takes another function as an
-- argument, the argument function is rendered in parentheses (as required by the
-- right-associative @->@ operator).
--
-- * All other type constructors are rendered prefix, including the list constructor @[]@
-- and the tuple constructors e.g. @(,,)@, along with any other type operators.
--
-- * All kind arguments to type constructors are rendered, with \@ prefixes. Any
-- non-atomic (that is, not simply a type constructor with no kind arguments) kinds
-- are enclosed in parentheses.
--
-- * Non-atomic arguments are enclosed in parentheses.
--
-- * Type synonyms are always expanded. This means that we get @[] Char@, not @String@,
-- and we get @TYPE ('BoxedRep 'Lifted)@, not @Type@.
defaultRenderTypeRep ::
  forall tn outer_t.
  TypeText tn =>
  TypeRep outer_t -> tn
defaultRenderTypeRep = go
  where
    go :: TypeRep t -> tn
    go (Fun arg@(Fun {}) res) = "(" <> go arg <> ") -> " <> go res
    go (Fun arg res) = go arg <> " -> " <> go res
    go (App t1 t2@(Con' _ [])) = go t1 <> " " <> go t2
    go (App t1 t2) = go t1 <> " (" <> go t2 <> ")"
    go (Con' tc kinds) = renderTyCon tc <> foldMap go_kind kinds

    go_kind :: SomeTypeRep -> tn
    go_kind (SomeTypeRep kind_rep@(Con' _ [])) = " @" <> go kind_rep
    go_kind (SomeTypeRep kind_rep) = " @(" <> go kind_rep <> ")"