tilia-0.0.2.0: src/Tilia/Fixity.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Working out the fixity of the operators a module uses.
module Tilia.Fixity
( -- * Fixities
OpName (..),
Direction (..),
Fixity (..),
defaultFixity,
-- * Module declarations
declaredFixities,
declaredNames,
moduleName,
-- * Module exports
ExportItem (..),
moduleExports,
declaredChildren,
moduleChildren,
-- * Module imports
Import (..),
ImportItem (..),
moduleImports,
mightBring,
surelyNames,
KnownModules (..),
noKnownModules,
Namespace (..),
Fixities,
inBothNamespaces,
UnreadModule (..),
ModuleChain (..),
spellModuleChain,
Scope (..),
Reach (..),
reachIn,
resolveScope,
-- * Answers
Provenance (..),
Resolution (..),
lookupFixity,
-- * What could not be answered
Unknown (..),
operatorsUsed,
unknownOperators,
operatorSpelling,
spellUnreadIn,
-- * What reading a module established
Established (..),
Exported (..),
exportedNames,
asExported,
)
where
import Data.Choice (Choice, isTrue)
import Data.Foldable (toList)
import Data.Generics.Schemes (listify)
import Data.List.NonEmpty (NonEmpty ((:|)), nonEmpty)
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text (Text)
import Data.Text qualified as T
import GHC.Hs hiding (Fixity, OpName)
import GHC.Types.Fixity qualified as GHC
import GHC.Types.Name.Occurrence (occNameString)
import GHC.Types.Name.Reader (RdrName (..), rdrNameOcc)
import GHC.Types.SrcLoc (GenLocated (..), unLoc)
import Tilia.Palette (Color (Place), Palette, paint)
----------------------------------------------------------------------------
-- Fixities
-- | An operator, spelled as it appears in an @infix@ declaration: @<+>@, or
-- @div@ for a function used infix in backticks.
newtype OpName = OpName Text
deriving (Eq, Ord, Show)
-- | Which way an operator associates.
data Direction = LeftAssoc | RightAssoc | NoAssoc
deriving (Eq, Show)
-- | A fixity: how tightly an operator binds, and which way it associates.
data Fixity = Fixity
{ fixityDirection :: Direction,
fixityPrecedence :: Int
}
deriving (Eq, Show)
-- | What an operator with no declaration in scope means: @infixl 9@.
defaultFixity :: Fixity
defaultFixity = Fixity LeftAssoc 9
----------------------------------------------------------------------------
-- Module declarations
-- | The fixities a module declares for its own operators.
declaredFixities :: HsModule GhcPs -> Fixities
declaredFixities hsModule =
Map.fromList
[ ((namespace, op), fixity)
| (specifier, op, fixity) <- concatMap (fromDecl . unLoc) (hsmodDecls hsModule),
namespace <- namespacesOf specifier op
]
where
(types, terms) = declaredNamespaces hsModule
namespacesOf specifier op = case specifier of
TypeNamespaceSpecifier _ -> [InTypes]
DataNamespaceSpecifier _ -> [InTerms]
NoNamespaceSpecifier ->
case ([InTypes | Set.member op types] <> [InTerms | Set.member op terms]) of
[] -> [InTypes, InTerms]
found -> found
fromDecl = \case
SigD _ sig -> fromSig sig
TyClD _ ClassDecl{tcdSigs} -> concatMap (fromSig . unLoc) tcdSigs
_ -> []
fromSig = \case
FixSig _ (FixitySig specifier names fixity) ->
[(specifier, opName (unLoc n), fromGhcFixity fixity) | n <- names]
_ -> []
-- | The names a module declares among types, and those it declares among
-- terms.
declaredNamespaces :: HsModule GhcPs -> (Set OpName, Set OpName)
declaredNamespaces hsModule =
( Set.fromList (concatMap (types . unLoc) decls),
Set.fromList (concatMap (terms . unLoc) decls)
)
where
decls = hsmodDecls hsModule
types = \case
TyClD _ d -> case d of
FamDecl _ FamilyDecl{fdLName} -> [opName (unLoc fdLName)]
SynDecl{tcdLName} -> [opName (unLoc tcdLName)]
DataDecl{tcdLName} -> [opName (unLoc tcdLName)]
ClassDecl{tcdLName, tcdATs} ->
opName (unLoc tcdLName)
: [opName (unLoc (fdLName (unLoc f))) | f <- tcdATs]
_ -> []
terms = \case
ValD _ b -> boundNames b
SigD _ sig -> signedNames sig
ForD _ f -> [opName (unLoc (fd_name f))]
TyClD _ d@DataDecl{} -> membersOf d
TyClD _ ClassDecl{tcdSigs} -> concatMap (classMethods . unLoc) tcdSigs
_ -> []
-- | The methods a class signature declares.
classMethods :: Sig GhcPs -> [OpName]
classMethods = \case
TypeSig _ ns _ -> fmap (opName . unLoc) ns
ClassOpSig _ _ ns _ -> fmap (opName . unLoc) ns
_ -> []
-- | The names a declaration carries under the name it declares: a data
-- type's constructors and record fields, a class's methods and the
-- families it keeps.
--
-- These are what @T(..)@ stands for, and each of them can carry a fixity of
-- its own—@:|@ is a constructor and @infixr 5@ all the same.
membersOf :: TyClDecl GhcPs -> [OpName]
membersOf = \case
DataDecl{tcdDataDefn} -> concatMap (fromCon . unLoc) (consOf (dd_cons tcdDataDefn))
ClassDecl{tcdSigs, tcdATs} ->
concatMap (classMethods . unLoc) tcdSigs
<> [opName (unLoc (fdLName (unLoc f))) | f <- tcdATs]
_ -> []
where
consOf :: DataDefnCons (LConDecl GhcPs) -> [LConDecl GhcPs]
consOf = toList
fromCon :: ConDecl GhcPs -> [OpName]
fromCon = \case
ConDeclGADT{con_names} -> fmap (opName . unLoc) (toList con_names)
ConDeclH98{con_name, con_args} ->
opName (unLoc con_name) : fieldNames con_args
fieldNames :: HsConDeclH98Details GhcPs -> [OpName]
fieldNames = \case
RecCon fields ->
[ opName (unLoc (foLabel (unLoc n)))
| f <- unLoc fields,
n <- cdrf_names (unLoc f)
]
_ -> []
-- | The names a signature is about, leaving fixity declarations aside.
signedNames :: Sig GhcPs -> [OpName]
signedNames = \case
TypeSig _ ns _ -> fmap (opName . unLoc) ns
ClassOpSig _ _ ns _ -> fmap (opName . unLoc) ns
PatSynSig _ ns _ -> fmap (opName . unLoc) ns
_ -> []
-- | Take a fixity as GHC presents it.
fromGhcFixity :: GHC.Fixity -> Fixity
fromGhcFixity (GHC.Fixity prec dir) = Fixity (fromGhcDirection dir) prec
-- | Take an associativity as GHC presents it.
fromGhcDirection :: GHC.FixityDirection -> Direction
fromGhcDirection = \case
GHC.InfixL -> LeftAssoc
GHC.InfixR -> RightAssoc
GHC.InfixN -> NoAssoc
-- | Render a parsed name as an operator name.
opName :: RdrName -> OpName
opName = OpName . T.pack . occNameString . rdrNameOcc
-- | Every name a module defines itself.
--
-- Not the same question as 'declaredFixities', which is about @infix@
-- declarations. This one is asked of an export list: a name a module
-- exports and also defines needs no chasing, and one it merely reexports
-- does.
declaredNames :: HsModule GhcPs -> Set OpName
declaredNames = Set.fromList . concatMap (fromDecl . unLoc) . hsmodDecls
where
fromDecl = \case
ValD _ b -> fromBind b
SigD _ sig -> fromSig sig
TyClD _ t -> fromTyCl t
ForD _ f -> [opName (unLoc (fd_name f))]
_ -> []
fromBind = boundNames
fromSig = \case
FixSig _ (FixitySig _ ns _) -> fmap (opName . unLoc) ns
sig -> signedNames sig
fromTyCl = \case
FamDecl _ (FamilyDecl{fdLName}) -> [opName (unLoc fdLName)]
SynDecl{tcdLName} -> [opName (unLoc tcdLName)]
d@DataDecl{tcdLName} -> opName (unLoc tcdLName) : membersOf d
d@ClassDecl{tcdLName} -> opName (unLoc tcdLName) : membersOf d
-- | The names a binding brings into being.
boundNames :: HsBind GhcPs -> [OpName]
boundNames = \case
FunBind _ n _ -> [opName (unLoc n)]
PatBind _ p _ _ -> [opName n | VarPat _ (L _ n) <- listify isVarPat p]
PatSynBind _ (PSB _ n _ _ _) -> [opName (unLoc n)]
_ -> []
where
isVarPat :: Pat GhcPs -> Bool
isVarPat = \case
VarPat{} -> True
_ -> False
-- | The module's own name, if it declares one.
moduleName :: HsModule GhcPs -> Maybe Text
moduleName = fmap (T.pack . moduleNameString . unLoc) . hsmodName
----------------------------------------------------------------------------
-- Module exports
-- | One entry of a module's export list.
data ExportItem
= -- | A name, which may or may not be declared in this module, under the
-- qualifier it was written with if it was written with one.
ExportName (Maybe Text) OpName
| -- | @T(..)@: the name, and with it whatever the module has to give
-- under that name. Which names those are cannot be read off the list;
-- it takes the declaration of @T@, or the module @T@ came from.
ExportAll (Maybe Text) OpName
| -- | @module M@, re-exporting everything that module brought in.
ExportModule Text
deriving (Eq, Show)
-- | A module's export list, or 'Nothing' if it has none.
moduleExports :: HsModule GhcPs -> Maybe [ExportItem]
moduleExports =
fmap (concatMap (fromIE . unLoc) . unLoc) . hsmodExports
where
fromIE = \case
IEVar _ n _ -> [named n]
IEThingAbs _ n _ -> [named n]
IEThingAll _ n _ -> [as ExportAll n]
IEThingWith _ n _ ns _ -> named n : fmap named ns
IEModuleContents _ m -> [ExportModule (T.pack (moduleNameString (unLoc m)))]
_ -> []
named = as ExportName
as item n =
let rdr = ieWrappedName (unLoc n)
in item (qualifierOf rdr) (opName rdr)
-- | The qualifier a name was written under.
qualifierOf :: RdrName -> Maybe Text
qualifierOf = \case
Qual m _ -> Just (T.pack (moduleNameString m))
_ -> Nothing
-- | What each type or class a module declares carries with it.
declaredChildren :: HsModule GhcPs -> Map OpName (Set OpName)
declaredChildren =
Map.fromListWith Set.union . concatMap (fromDecl . unLoc) . hsmodDecls
where
fromDecl = \case
TyClD _ d@DataDecl{tcdLName} -> [entry tcdLName d]
TyClD _ d@ClassDecl{tcdLName} -> [entry tcdLName d]
_ -> []
entry name d = (opName (unLoc name), Set.fromList (membersOf d))
-- | What a module offers under each name, as its export list offers it.
moduleChildren :: HsModule GhcPs -> Map OpName (Set OpName)
moduleChildren hsModule = case hsmodExports hsModule of
Nothing -> declared
Just items -> Map.fromListWith Set.union (concatMap (fromIE . unLoc) (unLoc items))
where
declared = declaredChildren hsModule
fromIE = \case
IEThingAll _ n _ ->
[(nameOf n, Map.findWithDefault Set.empty (nameOf n) declared)]
IEThingWith _ n _ ns _ -> [(nameOf n, Set.fromList (fmap nameOf ns))]
_ -> []
nameOf = opName . ieWrappedName . unLoc
----------------------------------------------------------------------------
-- Module imports
-- | One import declaration, reduced to what bears on fixity.
data Import = Import
{ -- | The module being imported.
importModule :: Text,
-- | Whether unqualified names are brought into scope. An import that is
-- @qualified@ brings none.
importQualified :: Bool,
-- | The name qualified uses go through: the alias if there is one,
-- otherwise the module's own name.
importAlias :: Text,
-- | The explicit list, if there is one, and whether it is a @hiding@
-- list.
--
-- Kept as written rather than as a set of names, because @T(..)@ says
-- what it brings in only once the module it comes from has been asked.
importNames :: Maybe (Bool, [ImportItem])
}
deriving (Eq, Show)
-- | One entry of an import list.
data ImportItem
= -- | A plain name.
ImportedName OpName
| -- | @T(..)@: the name, and everything the module offers under it.
ImportedAll OpName
| -- | @T(a, b)@: the name and the members written out beside it.
ImportedSome OpName [OpName]
deriving (Eq, Show)
-- | The imports of a module.
moduleImports ::
-- | Whether @ImplicitPrelude@ is on.
Choice "implicitPrelude" ->
-- | Parsed module.
HsModule GhcPs ->
[Import]
moduleImports implicitPrelude hsModule = prelude <> written
where
written = fmap (fromDecl . unLoc) (hsmodImports hsModule)
prelude
| not (isTrue implicitPrelude) = []
| any ((== "Prelude") . importModule) written = []
| otherwise =
[ Import
{ importModule = "Prelude",
importQualified = False,
importAlias = "Prelude",
importNames = Nothing
}
]
fromDecl d =
Import
{ importModule = modName (unLoc (ideclName d)),
importQualified = ideclQualified d /= NotQualified,
importAlias = maybe (modName (unLoc (ideclName d))) (modName . unLoc) (ideclAs d),
importNames = fromList <$> ideclImportList d
}
fromList (interpretation, names) =
( interpretation == EverythingBut,
mapMaybe (importedItem . unLoc) (unLoc names)
)
modName = T.pack . moduleNameString
-- | One entry of an import list, as written.
importedItem :: IE GhcPs -> Maybe ImportItem
importedItem = \case
IEVar _ n _ -> Just (ImportedName (nameOf n))
IEThingAbs _ n _ -> Just (ImportedName (nameOf n))
IEThingAll _ n _ -> Just (ImportedAll (nameOf n))
IEThingWith _ n _ ns _ -> Just (ImportedSome (nameOf n) (fmap nameOf ns))
_ -> Nothing
where
nameOf :: LIEWrappedName GhcPs -> OpName
nameOf = opName . ieWrappedName . unLoc
-- | Could this list bring the operator in?
mightBring ::
-- | What each name in the list keeps under it, where that is known.
Map OpName (Set OpName) ->
-- | The operator being looked for.
OpName ->
-- | The entries of the import list.
[ImportItem] ->
Bool
mightBring carries op = any $ \case
ImportedName n -> n == op
ImportedSome parent ns -> parent == op || op `elem` ns
ImportedAll parent -> maybe True (names parent) (Map.lookup parent carries)
where
names parent kids = parent == op || Set.member op kids
-- | Does this list certainly name the operator?
surelyNames ::
-- | What each name in the list keeps under it, where that is known.
Map OpName (Set OpName) ->
-- | The operator being looked for.
OpName ->
-- | The entries of the import list.
[ImportItem] ->
Bool
surelyNames carries op = any $ \case
ImportedName n -> n == op
ImportedSome parent ns -> parent == op || op `elem` ns
ImportedAll parent ->
maybe
(parent == op)
(names parent)
(Map.lookup parent carries)
where
names parent kids = parent == op || Set.member op kids
-- | What is known about the imported modules.
data KnownModules = KnownModules
{ -- | The fixities a module exports, or 'Nothing' if that could not be
-- determined.
knownFixities :: Text -> Maybe Fixities,
-- | What a module keeps under each of its names, so that a @T(..)@ in
-- an import list can be told what it brings in.
knownChildren :: Text -> Map OpName (Set OpName),
-- | The operators a module's export list names, following what it
-- reexports. 'Nothing' where a module it hands on could not be read.
knownExportNames :: Text -> Maybe (Set OpName),
-- | The modules reading a module went through before giving up, the one
-- it gave up on last. Asked only about modules 'knownFixities' could
-- not answer for, and only so that a message can name the module that
-- is really in the way.
knownChain :: Text -> [Text]
}
-- | No known modules.
noKnownModules :: KnownModules
noKnownModules =
KnownModules
{ knownFixities = const Nothing,
knownChildren = const Map.empty,
knownExportNames = const Nothing,
knownChain = const []
}
-- | Which of Haskell's two namespaces an operator is written in.
data Namespace = InTypes | InTerms
deriving (Eq, Ord, Show)
-- | The fixities a module offers, by the namespace each is written in.
type Fixities = Map (Namespace, OpName) Fixity
-- | Take fixities that say nothing about namespaces to govern both.
inBothNamespaces :: Map OpName Fixity -> Fixities
inBothNamespaces declared =
Map.fromList
[ ((namespace, op), fixity)
| (op, fixity) <- Map.toList declared,
namespace <- [InTypes, InTerms]
]
-- | An import whose module could not be read, and what is known about it
-- regardless.
data UnreadModule = UnreadModule
{ -- | The import as written.
unreadImport :: Import,
-- | The operators its export list names, as 'knownExportNames' gives
-- them.
unreadExportNames :: Maybe (Set OpName),
-- | What it keeps under each of its names, as 'knownChildren' gives
-- them, for expanding a @T(..)@ in the import list.
unreadChildren :: Map OpName (Set OpName),
-- | The modules reading went through before giving up, as 'knownChain'
-- gives them.
unreadChain :: [Text]
}
deriving (Eq, Show)
-- | An import that could not be read, and the way down to the module that
-- actually stopped us. The head is the import as the file being formatted
-- writes it, and the last name is where reading gave up.
newtype ModuleChain = ModuleChain (NonEmpty Text)
deriving (Eq, Show)
-- | A chain as it is shown, with the modules painted and arrows between
-- them.
spellModuleChain :: Palette -> ModuleChain -> Text
spellModuleChain palette (ModuleChain modules) =
T.intercalate " → " (fmap (paint palette Place) (toList modules))
-- | Every fixity a module can see, and how.
data Scope = Scope
{ -- | What is in scope for an operator written among types.
scopeInTypes :: Reach,
-- | What is in scope for one written among terms.
scopeInTerms :: Reach,
-- | The imports whose modules could not be read, and what is
-- nonetheless known about each.
scopeUnread :: [UnreadModule]
}
deriving (Eq, Show)
-- | What one namespace of a scope holds.
data Reach = Reach
{ -- | Reachable without qualification, with where it came from.
reachUnqualified :: Map OpName (Fixity, Provenance),
-- | Reachable as @M.op@, keyed by the alias actually written—or by the
-- module's own name, under which its own declarations are reachable.
reachQualified :: Map (Text, OpName) (Fixity, Provenance),
-- | Operators the imports bring in with two different fixities, as they
-- would have to be written to run into it: without a qualifier, or
-- under the alias the disagreeing imports share.
reachAmbiguous :: [(Maybe Text, OpName)]
}
deriving (Eq, Show)
-- | The half of a scope an operator written in this namespace is settled
-- against.
reachIn :: Namespace -> Scope -> Reach
reachIn = \case
InTypes -> scopeInTypes
InTerms -> scopeInTerms
-- | Work out what a module can see.
resolveScope ::
-- | Whether @ImplicitPrelude@ is on.
Choice "implicitPrelude" ->
-- | What is known about the modules this one imports.
KnownModules ->
-- | Parsed module.
HsModule GhcPs ->
Scope
resolveScope implicitPrelude known hsModule =
Scope
{ scopeInTypes = reachAmong InTypes,
scopeInTerms = reachAmong InTerms,
scopeUnread = unread
}
where
KnownModules{knownFixities = exportsOf, knownChildren, knownExportNames, knownChain} = known
exportNamesOf = knownExportNames
imports = moduleImports implicitPrelude hsModule
declared = declaredFixities hsModule
reachAmong namespace =
Reach
{ reachUnqualified = Map.union own (Map.map fst unqualified),
reachQualified = qualified,
reachAmbiguous =
[(Nothing, op) | op <- Map.keys (Map.filter snd unqualified)]
<> [(Just alias, op) | (alias, op) <- Map.keys (Map.filter snd qualifiedFrom)]
}
where
own = Map.map (,DeclaredHere) (fixitiesIn namespace declared)
offered m = fixitiesIn namespace <$> exportsOf m
unqualified =
Map.unionsWith
disagree
[ Map.map (,False) (visible offered i)
| i <- imports,
not (importQualified i)
]
qualified = Map.union ownQualified (Map.map fst qualifiedFrom)
ownQualified =
Map.fromList
[ ((m, op), entry)
| m <- toList (moduleName hsModule),
(op, entry) <- Map.toList own
]
qualifiedFrom =
Map.unionsWith
disagree
[ Map.mapKeys (importAlias i,) (Map.map (,False) (visible offered i))
| i <- imports
]
unread =
[ UnreadModule
{ unreadImport = i,
unreadExportNames = exportNamesOf (importModule i),
unreadChildren = knownChildren (importModule i),
unreadChain = knownChain (importModule i)
}
| i <- imports,
Nothing <- [exportsOf (importModule i)]
]
disagree (a, aBad) (b, bBad) = (a, aBad || bBad || fst a /= fst b)
visible offered i =
let exported =
Map.map (,DeclaredIn (importModule i)) $
fromMaybe Map.empty (offered (importModule i))
carries = knownChildren (importModule i)
in case importNames i of
Nothing -> exported
Just (True, hidden) ->
Map.filterWithKey
(\op _ -> not (surelyNames carries op hidden))
exported
Just (False, shown) ->
Map.filterWithKey (\op _ -> mightBring carries op shown) exported
-- | The fixities in one namespace, by the operator alone.
fixitiesIn :: Namespace -> Fixities -> Map OpName Fixity
fixitiesIn namespace declared =
Map.fromList
[ (op, fixity)
| ((n, op), fixity) <- Map.toList declared,
n == namespace
]
----------------------------------------------------------------------------
-- Answers
-- | Where a fixity came from.
--
-- Kept so that an answer can be explained, and so that
-- 'ReportDefault'—which is a real answer, not a guess—cannot be confused
-- with not having one.
data Provenance
= -- | An @infix@ declaration in the module being formatted.
DeclaredHere
| -- | An @infix@ declaration in the named imported module.
DeclaredIn Text
| -- | No declaration exists anywhere in scope, and every module in scope
-- was successfully consulted, so the Report's @infixl 9@ applies.
ReportDefault
deriving (Eq, Show)
-- | What is known about an operator at a use site.
data Resolution
= -- | Established, and here is where from.
Resolved Fixity Provenance
| -- | Not established.
Unresolved (NonEmpty ModuleChain)
deriving (Eq, Show)
-- | The fixity of an operator as this module sees it.
lookupFixity ::
-- | The scope.
Scope ->
-- | The namespace the operator is written in.
Namespace ->
-- | The qualifier written at the use site, if any.
Maybe Text ->
-- | Operator to resolve.
OpName ->
-- | The resolution.
Resolution
lookupFixity scope namespace qualifier op =
case fixityInScope scope namespace qualifier op of
Just (_, (fixity, provenance)) -> Resolved fixity provenance
Nothing -> case nonEmpty (unreadThatMightDeclare scope qualifier op) of
Nothing -> Resolved defaultFixity ReportDefault
Just missing -> Unresolved missing
-- | What the scope itself has for a use, and the namespace it came from.
fixityInScope ::
-- | The scope.
Scope ->
-- | The namespace the operator is written in.
Namespace ->
-- | The qualifier written at the use site, if any.
Maybe Text ->
-- | Operator to resolve.
OpName ->
-- | The fixity and where it came from, under the namespace that supplied
-- it. 'Nothing' where the scope has no answer.
Maybe (Namespace, (Fixity, Provenance))
fixityInScope scope namespace qualifier op =
case mapMaybe found (namespace : promotedFrom namespace) of
(answer : _) -> Just answer
[] -> Nothing
where
promotedFrom = \case
InTypes -> [InTerms]
InTerms -> []
found n =
(n,) <$> case qualifier of
Nothing -> Map.lookup op (reachUnqualified (reachIn n scope))
Just q -> Map.lookup (q, op) (reachQualified (reachIn n scope))
-- | The unread imports that could have declared this operator.
unreadThatMightDeclare ::
-- | The scope.
Scope ->
-- | The qualifier written at the use site, if any.
Maybe Text ->
-- | Operator being resolved.
OpName ->
-- | The imports that could hold the answer, each down to the module that
-- actually stopped us.
[ModuleChain]
unreadThatMightDeclare scope qualifier op =
[ ModuleChain (importModule (unreadImport u) :| unreadChain u)
| u <- scopeUnread scope,
reaches (unreadImport u),
brings u,
exports u
]
where
exports u = maybe True (Set.member op) (unreadExportNames u)
reaches i = case qualifier of
Nothing -> not (importQualified i)
Just q -> q == importAlias i
brings u = case importNames (unreadImport u) of
Nothing -> True
Just (True, hidden) -> not (surelyNames (unreadChildren u) op hidden)
Just (False, shown) -> mightBring (unreadChildren u) op shown
----------------------------------------------------------------------------
-- What could not be answered
-- | Why an operator's fixity could not be determined.
data Unknown
= -- | These imports could not be read, each given down to the module that
-- actually stopped us, and the declaration the answer depends on may be
-- in any of them.
NotRead (NonEmpty ModuleChain)
| -- | Two modules in scope bring it in with different fixities, so which
-- one applies cannot be read off the imports alone.
Ambiguous
deriving (Eq, Show)
-- | Every operator the module uses where its fixity decides the layout.
--
-- Only these positions. An operator chain in an expression and one in a type
-- are regrouped by precedence, so getting the precedence wrong changes what
-- the code means. Everywhere else—a section, the left-hand side of a
-- definition, an @infix@ declaration—the operator stands on its own and
-- nothing is regrouped around it.
operatorsUsed :: HsModule GhcPs -> [(Namespace, (Maybe Text, OpName))]
operatorsUsed hsModule =
fmap (named InTerms) inExpressions <> fmap (named InTypes) inTypes
where
inExpressions =
[ n
| e :: HsExpr GhcPs <- listify (const True) hsModule,
OpApp _ _ op _ <- [e],
HsVar _ (L _ n) <- [unLoc op]
]
inTypes =
[ n
| t :: HsType GhcPs <- listify (const True) hsModule,
HsOpTy _ _ _ (L _ n) _ <- [t]
]
named namespace n =
(namespace, (qualifierOf n, OpName (T.pack (occNameString (rdrNameOcc n)))))
-- | The operators this module uses that the scope cannot settle, as the
-- module writes them.
--
-- Empty is the only acceptable answer: an operator whose fixity is not
-- known cannot be laid out, only guessed at.
unknownOperators :: Scope -> HsModule GhcPs -> [((Maybe Text, OpName), Unknown)]
unknownOperators scope hsModule =
Map.toList (Map.fromList (mapMaybe unsettled (operatorsUsed hsModule)))
where
ambiguous namespace = Set.fromList (reachAmbiguous (reachIn namespace scope))
unsettled (namespace, (qualifier, op)) =
case fixityInScope scope namespace qualifier op of
Just (answering, _)
| Set.member (qualifier, op) (ambiguous answering) ->
Just ((qualifier, op), Ambiguous)
| otherwise -> Nothing
Nothing -> case nonEmpty (unreadThatMightDeclare scope qualifier op) of
Just missing -> Just ((qualifier, op), NotRead missing)
Nothing -> Nothing
-- | An operator as a use site writes it, qualifier and all.
operatorSpelling :: Maybe Text -> OpName -> Text
operatorSpelling qualifier (OpName op) = maybe "" (<> ".") qualifier <> op
-- | Spell out where an unsettled operator may have come from, and the fact
-- that this run could not read any of it.
spellUnreadIn ::
-- | Whether there is anybody there to see color.
Palette ->
-- | The chains, as 'Unresolved' gives them.
NonEmpty ModuleChain ->
Text
spellUnreadIn palette missing =
T.intercalate " or " (fmap (spellModuleChain palette) (toList missing))
<> ", "
<> ofThose
where
ofThose = case toList missing of
[_] -> "which this run could not read"
[_, _] -> "neither of which this run could read"
_ -> "none of which this run could read"
----------------------------------------------------------------------------
-- What reading a module established
-- | What reading a module established about its operators.
data Established
= -- | It was read, and declares these.
Declares Fixities
| -- | It could not be read. The expensive answer of the two, because
-- reaching it means exhausting every way of reading the module.
Unreadable (Maybe Text)
deriving (Eq, Show)
-- | What reading a module established about its export list.
data Exported
= -- | The list names these, and they are all the module can supply.
Exports (Set OpName)
| -- | Nothing that can be enumerated: the list hands whole modules on, or
-- the source would not parse.
Untellable
deriving (Eq, Show)
-- | What 'resolveScope' makes of it.
exportedNames :: Exported -> Maybe (Set OpName)
exportedNames = \case
Exports names -> Just names
Untellable -> Nothing
-- | What to write down for an answer the reader worked out.
asExported :: Maybe (Set OpName) -> Exported
asExported = maybe Untellable Exports