packages feed

smuggler2-0.3.6.2: src/Smuggler2/Imports.hs

{-# LANGUAGE CPP #-}
-- |
-- Description:  A replacement for 'RnNames.getMinimalImports' that attempts
--               to handle patterns and types, which is not done correctly in GHC 8.10.1 and
--               earlier.
module Smuggler2.Imports (getMinimalImports) where

import Avail ( AvailInfo(..) )
import BasicTypes ( StringLiteral(sl_fs) )
import FieldLabel ( FieldLbl(flIsOverloaded, flLabel, flSelector) )
import GHC
    ( GhcRn,
      IE(IEThingAbs, IEThingAll, IEThingWith, IEVar),
      IEWildcard(NoIEWildcard),
      IEWrappedName(IEName, IEPattern, IEType),
      ImportDecl(ImportDecl, ideclHiding, ideclName, ideclPkgQual,
                 ideclSource),
      LIEWrappedName,
      LImportDecl )
import HscTypes -- earlier versions of Ghc don't have ModIface_
import LoadIface ( loadSrcInterface )
import Name ( HasOccName(..), isDataOcc, isTcOcc )
import Outputable ( Outputable(ppr), text, (<+>) )
#if MIN_VERSION_GLASGOW_HASKELL(8,8,0,0)
import RdrName ( gresToAvailInfo )
#endif
import RnNames ( ImportDeclUsage )
import SrcLoc ( GenLocated(L), Located, noLoc )
import TcRnMonad ( RnM )
import Language.Haskell.GHC.ExactPrint.Types ( noExt )

{-
Note [Partial export]
~~~~~~~~~~~~~~~~~~~~~
Suppose we have

   module A( op ) where
     class C a where
       op :: a -> a

   module B where
   import A
   f = ..op...

Then the minimal import for module B is
   import A( op )
not
   import A( C( op ) )
which we would usually generate if C was exported from B.  Hence
the (x `elem` xs) test when deciding what to generate.


Note [Overloaded field import]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
On the other hand, if we have

    {-# LANGUAGE DuplicateRecordFields #-}
    module A where
      data T = MkT { foo :: Int }

    module B where
      import A
      f = ...foo...

then the minimal import for module B must be
    import A ( T(foo) )
because when DuplicateRecordFields is enabled, field selectors are
not in scope without their enclosing datatype.

-}

-- | Attempt to fix
-- <https://hackage.haskell.org/package/ghc/docs/RnNames.html#v:getMinimalImports>
getMinimalImports :: [ImportDeclUsage] -> RnM [LImportDecl GhcRn]
getMinimalImports = mapM mk_minimal
  where
    mk_minimal (L l decl, used_gres, unused)
      | null unused
      , Just (False, _) <- ideclHiding decl
      = return (L l decl)
      | otherwise
      = do { let ImportDecl { ideclName    = L _ mod_name
                            , ideclSource  = is_boot
                            , ideclPkgQual = mb_pkg } = decl
           ; iface <- loadSrcInterface doc mod_name is_boot (fmap sl_fs mb_pkg)
#if MIN_VERSION_GLASGOW_HASKELL(8,8,0,0)
           -- TODO not sure when this was introduced
           ; let used_avails = gresToAvailInfo used_gres
                 lies = map (L l) (concatMap (to_ie iface) used_avails)
#else
           -- used_gres are actually already AvailInfo in earlier versions of
           -- GHC
           ; let lies = map (L l) (concatMap (to_ie iface) used_gres)
#endif
           ; return (L l (decl { ideclHiding = Just (False, L l lies) })) }
      where
        doc = text "Compute minimal imports for" <+> ppr decl

    to_ie :: ModIface -> AvailInfo -> [IE GhcRn]
    -- The main trick here is that if we're importing all the constructors
    -- we want to say "T(..)", but if we're importing only a subset we want
    -- to say "T(A,B,C)".  So we have to find out what the module exports.
    to_ie _ (Avail n) -- An ordinary identifier (eg, var, data constructor)
       = [IEVar noExt (to_ie_post_rn_var $ noLoc n)]
    to_ie _ (AvailTC n [m] []) -- type or class with absent () list
       | n==m = [IEThingAbs noExt (to_ie_post_rn_name $ noLoc n)]
    to_ie iface (AvailTC n ns fs)
      = case [(xs,gs) |  AvailTC x xs gs <- mi_exports iface
                 , x == n
                 , x `elem` xs    -- Note [Partial export]
                 ] of
           -- class / type with methods / constructors s
           [xs] | all_used xs -> [IEThingAll noExt (to_ie_post_rn_name $ noLoc n)] -- (..)

                | isTcOcc (occName n) -> -- typeclass -- @class Functor ...
                   [IEThingWith noExt (to_ie_post_rn_name $ noLoc n) NoIEWildcard
                                (map (to_ie_post_rn_varn . noLoc) (filter (/= n) ns))
                                (map noLoc fs)]
                                          -- Note [Overloaded field import]

                | otherwise   -> -- type constructor (ie, @data X =@)
                   [IEThingWith noExt (to_ie_post_rn_name $ noLoc n) NoIEWildcard
                                (map (to_ie_post_rn_cname . noLoc) (filter (/= n) ns))
                                (map noLoc fs)]

           -- record type
           _other | all_non_overloaded fs
                           -> map (IEVar noExt . to_ie_post_rn_name . noLoc) $ ns
                                 ++ map flSelector fs
                  | otherwise -> -- DuplicateRecordFields is applicable
                      [IEThingWith noExt (to_ie_post_rn_name $ noLoc n) NoIEWildcard
                                (map (to_ie_post_rn_cname . noLoc) (filter (/= n) ns))
                                (map noLoc fs)]
        where

          fld_lbls = map flLabel fs

          all_used (avail_occs, avail_flds)
              = all (`elem` ns) avail_occs
                    && all ((`elem` fld_lbls) . flLabel) avail_flds

          all_non_overloaded = not . any flIsOverloaded


-- The following were originally one function.  Splitting it out this way allows
-- different variants to be used in different contexts above.  This seems to
-- work, but needs review.  Eg, do we need to use an isSymOcc in some cases?
to_ie_post_rn_name :: Located name -> LIEWrappedName name
to_ie_post_rn_name (L l n) = L l (IEName (L l n))

to_ie_post_rn_var :: (HasOccName name) => Located name -> LIEWrappedName name
to_ie_post_rn_var (L l n)
  | isDataOcc $ occName n = L l (IEPattern (L l n))
  | otherwise = L l (IEName (L l n))

to_ie_post_rn_varn :: (HasOccName name) => Located name -> LIEWrappedName name
to_ie_post_rn_varn (L l n)
  | isTcOcc $ occName n = L l (IEType (L l n))
  | otherwise = L l (IEName (L l n))

to_ie_post_rn_cname :: (HasOccName name) => Located name -> LIEWrappedName name
to_ie_post_rn_cname (L l n)
  | isTcOcc $ occName n = L l (IEType (L l n))
  | otherwise = L l (IEName (L l n))

-- Notes
--
-- https://gitlab.haskell.org/ghc/ghc/-/wikis/pattern-synonyms/associating-synonyms

-- An import is
-- a var
-- a tycls -> [(..) | ( var1 , … , varn )]          -- class, etc
-- a tycon -> [ (..) | ( cname1 , … , cnamen )]     -- data

-- cname -> var | con
-- var -> varid | ( varsym )  -- (does not start with :)
-- con -> conid | ( consym )  -- (starts with :)

-- GHC User Guide 8.7.3
-- The name of the pattern synonym is in the same namespace as proper data constructors.
-- Like normal data constructors, pattern synonyms can be imported through associations
-- with a type constructor or independently.
-- To export them *on their own*, in an export or import specification,
-- you must prefix pattern names with the pattern keyword
--
-- GHC User Guide 9.9.5
--The form C(.., mi, .., type Tj, ..), where C is a class, names the class C, and the
--specified methods mi and associated types Tj. The types need a keyword “type” to distinguish
--them from data constructors.
--
--Whenever there is no export list and a data instance is defined, the corresponding
--data family type constructor is exported along with the new data constructors, regardless of
--whether the data family is defined locally or in another module.

-- isVarOcc -> variable name
-- isTvOcc -> is type variable
-- isTcOcc -> is type class name
-- isValOcc -- either in the variable or data constructor namespaces
-- isDataOcc -- Data constructor
-- isDataSymOcc -> Data contructuctor starting with a symbol
-- isSymOcc -> operator(data constructor, variable, etc)
--
-- So there are
-- var -> IEName
-- tycon -> can have IEPattern
-- tyclas -> can have IEType type (:+:)