Chapter 2
The renamer
How every occurrence gets tied to the thing it names, and how the phase that is supposedly "just name resolution" ends up re-associating your operators and quietly desugaring half of Haskell's syntactic sugar.
Where this lives in the tree
-
GHC/Rename/Module.hsthe entry point, and dependency analysis of declarations -
GHC/Rename/Env.hslookup, the heart of name resolution -
GHC/Rename/Expr.hsrenaming expressions, and expanding rebindable syntax -
GHC/Types/Name/Reader.hsRdrName, GlobalRdrEnv, LocalRdrEnv
The parser handed over a tree in which every identifier is still just text. map
is the string “map”; whether that means Prelude.map, a local binding, or
nothing at all is an open question. The renamer answers it, for every occurrence
in the module.
That sounds mechanical, and the first half of it is. The interesting part is what else GHC decided to put here: because the renamer is the first phase that knows what names mean, several problems that look like they belong elsewhere can only be solved once, right here.
RdrName becomes Name
The input side is RdrName, which is simply what the user wrote:
data RdrName
= Unqual OccName -- ^ Unqualified name, e.g. @x@, @y@ or @Foo@
| Qual ModuleName OccName
| Orig Module OccName
| Exact Name
An OccName is a string plus a namespace: Haskell lets you use the same
spelling for a variable, a type constructor, and a data constructor without
collision, so “which namespace” is part of the name itself.
The output side is Name:
data Name = Name
{ n_sort :: NameSort -- ^ external, internal, system, wired-in
, n_occ :: OccName -- ^ its occurrence name
, n_uniq :: {-# UNPACK #-} !Unique
}
The Unique is what makes the rest of the compiler work. After renaming, two
Names refer to the same entity exactly when their uniques are equal: no string
comparison, no scope walking, no ambiguity. Every later phase relies on this.
Two environments drive the translation:
data LocalRdrEnv = LRE { lre_env :: OccEnv Name, ... }
type GlobalRdrEnv = GlobalRdrEnvX GREInfo
type GlobalRdrEnvX info = OccEnv [GlobalRdrEltX info]
Note the asymmetry. The local environment maps an OccName to one Name:
lexical scoping means the innermost binding wins and shadowing is resolved by
construction. The global environment maps to a list, because a name can
legitimately be in scope from several imports at once. That is not an error until
someone actually uses it ambiguously, and it is why GHC can tell you an occurrence
is ambiguous while pointing at every import responsible.
It runs in the typechecker’s monad
type RnM = TcRn
The renamer does not have a monad of its own. RnM is a synonym for the
typechecker’s TcRn, and the two phases share their environment type. This is
not an accident of history: renaming and typechecking are interleaved at the top
level, because Template Haskell splices must be renamed, typechecked, run, and
the results renamed again before the surrounding module can be finished.
The practical consequence for reading the code: you will see the renamer calling
things that look like typechecker utilities, and the shared TcGblEnv threading
through both. That is by design, not layering violation.
Fixity: the loose end from the parser
The parser chapter ended with x |+| y * 2 sitting in the tree as a flat chain,
because the parser cannot know any operator’s fixity. The renamer can (it has
just resolved those operators to Names, so it can look up their declared
fixity), and so it re-associates the tree here:
mkOpAppRn :: NegationHandling
-> LHsExpr GhcRn -- Left operand; already rearranged
-> LHsExpr GhcRn -> Fixity -- Operator and fixity
-> LHsExpr GhcRn -- Right operand
-> RnM (HsExpr GhcRn)
-- (e1a `op1` e1b) `op2` e2
mkOpAppRn negation_handling e1@(L _ (OpApp fix1 e1a op1 e1b)) op2 fix2 e2
| nofix_error
= do precParseErr (get_op op1,fix1) (get_op op2,fix2)
return (OpApp fix2 e1 op2 e2)
| associate_right = do
new_e <- mkOpAppRn negation_handling e1b op2 fix2 e2
return (OpApp fix1 e1a op1 (L loc' new_e))
where
(nofix_error, associate_right) = compareFixity fix1 fix2
Three outcomes, and the middle one is the one people forget exists: two operators
of equal precedence and incompatible associativity is a parse error, reported
here rather than by the parser. compareFixity returns exactly that flag.
There is a matching mkHsOpTyRn for types, because type-level operators need the
same treatment. And fixity lookup itself has a wrinkle worth seeing, since a
fixity declaration does not say which namespace it means:
A fixity declaration like
infixr 2 ?
can refer to a value-level operator, e.g.:
(?) :: String -> String -> String
or a type-level operator, like:
data (?) a b = A a | B b
so we extend the lookup of the reader name '?' to the TcClsName namespace, as
well as the original namespace.
The extended lookup is also used in other places, like resolution of
deprecation declarations, and lookup of names in GHCi. The renamer desugars
Here is the part that surprises people. The renamer does not merely resolve names; for overloaded and rebindable constructs it rewrites the tree into something else entirely, before the typechecker ever sees it.
Nomenclature
* Expansion (`HsExpr GhcRn -> HsExpr GhcRn`): expand between renaming and
typechecking, using the `XXExprGhcRn` constructor of `HsExpr`.
* Desugaring (`HsExpr GhcTc -> Core.Expr`): convert the typechecked `HsSyn` to Core. This is done in GHC.HsToCore
For overloaded constructs (overloaded literals, lists, strings), and
rebindable constructs (e.g. if-then-else), our general plan is this,
using overloaded labels #foo as an example:
* In the RENAMER: transform
HsOverLabel "foo"
==> XExpr (ExpandedThingRn (HsOverLabel #foo)
(fromLabel `HsAppType` "foo"))
We write this more compactly in concrete-syntax form like this
#foo ==> fromLabel @"foo"
Recall that in (ExpandedThingRn orig expanded), 'orig' is the original term
the user wrote, and 'expanded' is the expanded or desugared version
to be typechecked.
* In the TYPECHECKER: typecheck the expansion, in this case
fromLabel @"foo"
The typechecker (and desugarer) will never see HsOverLabel
In effect, the renamer does a bit of desugaring. Recall GHC.Hs.Expr
Note [Rebindable syntax and XXExprGhcRn], which describes the use of XXExprGhcRn.
RebindableSyntax:
If RebindableSyntax is off we use the built-in 'fromLabel', defined in
GHC.Builtin.Names.fromLabelClassOpName
If RebindableSyntax if ON, we look up "fromLabel" in the environment
to get whichever one is in scope.
This is accomplished by lookupSyntaxName, and it applies to all the
constructs below.
See also Note [Handling overloaded and rebindable patterns] in GHC.Rename.Pat
for the story with patterns.
Show the rest of this Note (68 more lines)
Here are the expressions that we transform in this way. Some are uniform,
but several have a little bit of special treatment:
* HsIf (if-the-else)
if b then e1 else e2 ==> ifThenElse b e1 e2
We do this /only/ if rebindable syntax is on, because the coverage
checker looks for HsIf (see GHC.HsToCore.Ticks.addTickHsExpr)
That means the typechecker and desugarer need to understand HsIf
for the non-rebindable-syntax case.
* OverLabel (overloaded labels, #lbl)
#lbl ==> fromLabel @"lbl"
As ever, we use lookupSyntaxName to look up 'fromLabel'
See Note [Overloaded labels] below
* ExplicitList (explicit lists [a,b,c])
When (and only when) OverloadedLists is on
[e1,e2] ==> fromListN 2 [e1,e2]
NB: the type checker and desugarer still see ExplicitList,
but to them it always means the built-in lists.
* SectionL and SectionR (left and right sections)
(`op` e) ==> rightSection op e
(e `op`) ==> leftSection (op e)
where `leftSection` and `rightSection` are representation-polymorphic
wired-in Ids. See Note [Left and right sections]
* To understand why expansions for `OpApp` is done in `GHC.Tc.Gen.Head.splitHsApps`
see Note [Doing XXExprGhcRn in the Renamer vs Typechecker] below.
* RecordUpd: we desugar record updates into case expressions,
in GHC.Tc.Gen.Expr.tcExpr.
Example:
data T p q = T1 { x :: Int, y :: Bool, z :: Char }
| T2 { v :: Char }
| T3 { x :: Int }
| T4 { p :: Float, y :: Bool, x :: Int }
| T5
e { x=e1, y=e2 }
===>
let { x' = e1; y' = e2 } in
case e of
T1 _ _ z -> T1 x' y' z
T4 p _ _ -> T4 p y' x'
See Note [Record Updates] in GHC.Tc.Gen.Expr for more details.
To understand Why is this done in the typechecker, and not in the renamer
see Note [Doing XXExprGhcRn in the Renamer vs Typechecker]
* HsDo: We expand `HsDo` statements in `Ghc.Tc.Gen.Do`.
- For example, a user written code:
do { x <- e1 ; g x ; return (f x) }
is expanded to:
(>>=) e1
(\x -> ((>>) (g x)
(return (f x))))
See Note [Expanding HsDo with XXExprGhcRn] in `Ghc.Tc.Gen.Do` for more details.
To understand why is this done in the typechecker and not in the renamer.
See Note [Doing XXExprGhcRn in the Renamer vs Typechecker] Read the nomenclature at the top carefully, because GHC is precise about it:
expansion is HsExpr GhcRn -> HsExpr GhcRn and happens here; desugaring is
HsExpr GhcTc -> Core.Expr and happens much later. The Note’s own summary,
“in effect, the renamer does a bit of desugaring”, is the honest description.
The reason this must happen at renaming time rather than later is
RebindableSyntax. With the extension off, #foo expands using the built-in
fromLabel. With it on, it expands using whatever fromLabel is in scope.
Only the renamer knows what is in scope. By the time the typechecker runs, the
question is already settled, and HsOverLabel no longer exists in the tree.
The ExpandedThingRn constructor keeps both halves (the original term the user
wrote and the expansion to be typechecked) so error messages can still talk
about the program you actually wrote rather than the one GHC rewrote it into.
Order out of a flat file
Haskell declarations may appear in any order and may be mutually recursive. The typechecker cannot exploit that: it needs to generalise a binding group before typechecking anything that uses it, so it needs the groups, in dependency order. Computing them requires knowing what refers to what. That, again, is something only the renamer knows.
So the renamer performs strongly-connected-component analysis over the declarations and hands the typechecker an ordered sequence of binding groups. Type and class declarations get their own analysis:
A TyClGroup represents a strongly connected component of type/class/instance
decls, together with the role annotations and standalone kind signatures for the
type/class declarations. The renamer uses strongly connected component analysis
to build these groups. We do this for a number of reasons:
* Improve kind error messages. Consider
data T f a = MkT f a
data S f a = MkS f (T f a)
This has a kind error, but the error message is better if you
check T first, (fixing its kind) and *then* S. If you do kind
inference together, you might get an error reported in S, which
is jolly confusing. See #4875
* Increase kind polymorphism. See GHC.Tc.TyCl
Note [Grouping of type and class declarations]
What about instances? Based on a number of tickets (#12088, #12239, #14668,
#15561, #16410, #16448, #16693, #19611, #20875, #21172, #22257, #25238, #25834,
etc) we concluded that we cannot handle them at this stage.
Show the rest of this Note (139 more lines)
It is not possible, by looking at the free variables of a declaration, to determine which instances a declaration depends on; furthermore, it is not possible to discover dependencies between instances, for the same reason. Previously GHC inserted instances at the earliest positions where their FVs are bound, but it only helped with a subset of tickets. The current approach is to accept that the dependency analysis here is incomplete and recover in the kind checker with a retrying mechanism. See Note [Retrying TyClGroups] in GHC.Tc.TyCl So much for why we want SCCs. What about how and when we construct them? First, an overview: (TCDEP1) Flatten TyClGroups from the parser (TCDEP2) Rename the type/class declarations, standalone kind signatures, role declarations, and instances individually (TCDEP3) Preprocess FVs and build a dependency graph (TCDEP4) Find strongly connected components (SCCs) of declarations (TCDEP5) Attach roles and kind signatures to the appropriate SCC (TCDEP6) Create one singleton "SCC" per instance and put them at the end And now the deep dive: (TCDEP1) We start with a `HsGroup GhcPs`, containing a `[TyClGroup GhcPs]`: a big pile of declarations. It is not important how the parser distributes declarations across those TyClGroups, as the first thing we do in `rnTyClDecls` is flatten them using a few helpers: tyClGroupTyClDecls = Data.List.concatMap group_tyclds tyClGroupInstDecls = Data.List.concatMap group_instds tyClGroupRoleDecls = Data.List.concatMap group_roles tyClGroupKindSigs = Data.List.concatMap group_kisigs In practice, the parser just puts all declarations in a single `TyClGroup`, so the `concatMap` is a no-op. (TCDEP2) Rename each declaration separately, yielding the following lists in `rnTyClDecls`: tycls_w_fvs :: [(LTyClDecl GhcRn, FreeVars)] instds_w_fvs :: [(LInstDecl GhcRn, FreeVars)] kisigs_w_fvs :: [(LStandaloneKindSig GhcRn, FreeVars)] role_annots :: [LRoleAnnotDecl GhcRn] The `FreeVars` are the free type/data constructors of the decl. For example: type family F (a :: k) -- FVs: {} data X = MkX Char (Maybe X) -- FVs: {Char, Maybe, X} data Y = MkY X (Maybe Y) -- FVs: {Maybe, Y, X} type instance F MkX = X -- FVs: {F, MkX, X} type instance F MkY = Int -- FVs: {F, MkY, Int} (TCDEP3) Build a graph where each node is a `TyClDecl` keyed by its name, and its `FreeVars` give rise to edges. Happens in `depAnalTyClDecls`. Examples: data A x = MkA x -- node `A`, edges: {} data B x = MkB (A x) -- node `B`, edges: {B -> A} data C = MkC (B C) -- node `C`, edges: {C -> B, C -> C} The `FreeVars` are not used "as is" to create the edges. They first undergo a few transformations. (TCDEP3.fvs_kisig) If a standalone kind signature is present, add its free variables to those of the declaration. Consider: data A = MkA data B = MkB type P :: A -> Type -- sig FVs: {A, Type} data P x = MkP (Proxy MkB) -- decl FVs: {Proxy, MkB} By adding the sig and decl FVs together, we get {A, Type, Proxy, MkB}. Then proceed to the next step. (TCDEP3.fvs_parent) Replace any mention of a (promoted) data constructor with its parent TyCon. Consider the FVs from the previous step: the name set {A, Type, Proxy, MkB} turns into {A, Type, Proxy, B} MkB does not get its own node in the graph, so an edge to it must actually point to B. (TCDEP3.fvs_nogbl) Filter out references to type constructors outside this `HsGroup`. They just clutter things up: the name set {A, Type, Proxy, B} turns into {A, B} Note [Prepare TyClGroup FVs] describes these transformations in more detail. Back to our `P` example, the final nodes and edges are as follows: data A = MkA -- node `A`, edges: {} data B = MkB -- node `B`, edges: {} type P :: A -> Type data P x = MkP (Proxy MkB) -- node `P`, edges: {P -> A, P -> B} (TCDEP4) Find strongly connected components (SCCs) of `TyClDecl`s. This happens immediately after building the dependency graph in `depAnalTyClDecls`. As the result, in `rnTyClDecls` we get tycl_sccs :: [SCC (LTyClDecl GhcRn, NameSet)] These SCCs are topologically sorted, but only according to lexical dependencies (i.e. dependencies that can be found by looking at the FVs). Non-lexical dependencies (i.e. dependencies on instances) are ignored because they can't be reliably found prior to type checking. More on that in Note [Retrying TyClGroups] in GHC.Tc.TyCl. (TCDEP5) For each SCC, create a `TyClGroup GhcRn`. Standalone kind signatures and role annotations are looked up by name and included in the same `TyClGroup` as the corresponding type/class declarations. The extension field `group_ext` of `TyClGroup GhcRn` contains the dependencies of the SCC computed from FVs in step (TCDEP3), but /excluding/ the type constructors bound by the group itself. Example: TyClGroup: binds {Z} depends on {} data Z = MkZ -- FVs: {} TyClGroup: binds {X, Y} depends on {Z} rather than {X, Y, Z} data X = MkX Y Z -- FVs: {Y, Z} data Y = MkY X Z -- FVs: {X, Z} Reason: `isReadyTyClGroup` in GHC.Tc.TyCl is a function that checks whether all of a TyClGroup's dependencies are present in the type checking env, and we wouldn't want it to consider a group to be "blocked" on its own declarations. (TCDEP6) For each instance, create a singleton `TyClGroup GhcRn`, and put them all at the end, where their lexical dependencies are surely satisfied. More on that in Note [Put instances at the end]. The `group_ext` field in an instance TyClGroup is set to the FVs of the instance, preprocessed much in the same way as declaration FVs in step (TCDEP3). See Note [Prepare TyClGroup FVs] for details.
This is also where the unused-binding and unused-import warnings come from: the renamer is tracking free variables to do the dependency analysis anyway, so it already knows what was never mentioned.
Seeing it happen
The Renamed tab is this chapter’s output. Compare it with Parsed:
What you wrote.
-- | A deliberately small module aimed at the *front* of the pipeline.
--
-- The point of interest here is the parsed AST rather than the Core: layout
-- (no braces or semicolons are written, yet the parser inserts them),
-- operator sections, `do` notation, and an infix operator whose fixity is not
-- known until the renamer runs.
module Syntax where
infixl 6 |+|
(|+|) :: Int -> Int -> Int
x |+| y = x + y * 2
total :: [Int] -> Int
total xs = sum (map (|+| 1) xs)
greet :: Maybe String -> String
greet name = case name of
Just n -> "hello, " ++ n
Nothing -> "hello"
collect :: [Int] -> [Int]
collect xs = do
x <- xs
let doubled = x * 2
pure doubledThe same tree, pretty-printed back out as Haskell: note what layout became.
module Syntax where
infixl 6 |+|
(|+|) :: Int -> Int -> Int
x |+| y = x + y * 2
total :: [Int] -> Int
total xs = sum (map (|+| 1) xs)
greet :: Maybe String -> String
greet name
= case name of
Just n -> "hello, " ++ n
Nothing -> "hello"
collect :: [Int] -> [Int]
collect xs
= do x <- xs
let doubled = x * 2
pure doubledmodule Syntax where
infixl 6 |+|
(|+|) :: Int -> Int -> Int
x |+| y = x + y * 2
total :: [Int] -> Int
total xs = sum (map (|+| 1) xs)
greet :: Maybe String -> String
greet name
= case name of
Just n -> "hello, " ++ n
Nothing -> "hello"
collect :: [Int] -> [Int]
collect xs
= do x <- xs
let doubled = x * 2
pure doubledEvery name now resolved to the specific entity it refers to. The readable view suppresses the qualifiers; flip Full detail to see them.
infixl 6 |+|
(|+|) :: Int -> Int -> Int
x |+| y = x + y * 2
total :: [Int] -> Int
total xs = sum (map (|+| 1) xs)
greet :: Maybe String -> String
greet name
= case name of
Just n -> "hello, " ++ n
Nothing -> "hello"
collect :: [Int] -> [Int]
collect xs
= do x <- xs
let doubled = x * 2
pure doubledinfixl 6 Syntax.|+|
(Syntax.|+|) :: Int -> Int -> Int
x Syntax.|+| y = x + y * 2
Syntax.total :: [Int] -> Int
Syntax.total xs = sum (map (Syntax.|+| 1) xs)
Syntax.greet :: Maybe String -> String
Syntax.greet name
= case name of
Just n -> "hello, " ++ n
Nothing -> "hello"
Syntax.collect :: [Int] -> [Int]
Syntax.collect xs
= do x <- xs
let doubled = x * 2
pure doubledThe elaborated program: AbsBinds wrappers, dictionary arguments at call sites, and Evidence lines naming the instance each solved constraint resolved to.
$trModule = Module (TrNameS "main"#) (TrNameS "Syntax"#)
AbsBinds [] []
{Exports: [collect <= collect
wrap: <>]
Exported types: collect :: [Int] -> [Int]
Binds: collect xs
= do x <- xs
let doubled = x * 2
pure doubled
Evidence: [EvBinds{}]}
AbsBinds [] []
{Exports: [greet <= greet
wrap: <>]
Exported types: greet :: Maybe String -> String
Binds: greet name
= case name of
Just{EvBinds{}} n -> "hello, " ++ n
Nothing{EvBinds{}} -> "hello"
Evidence: [EvBinds{}]}
AbsBinds [] []
{Exports: [|+| <= |+|
wrap: <>]
Exported types: (|+|) :: Int -> Int -> Int
Binds: x |+| y = x + y * 2
Evidence: [EvBinds{}]}
AbsBinds [] []
{Exports: [total <= total
wrap: <>]
Exported types: total :: [Int] -> Int
Binds: total xs
= sum @[] $dFoldable @Int $dNum (map @Int @Int (|+| 1) xs)
Evidence: [EvBinds{}]}Syntax.$trModule
= GHC.Internal.Types.Module
(GHC.Internal.Types.TrNameS "main"#)
(GHC.Internal.Types.TrNameS "Syntax"#)
AbsBinds [] []
{Exports: [collect <= collect
wrap: <>]
Exported types: collect :: [Int] -> [Int]
[LclId]
Binds: collect xs
= do x <- xs
let doubled = x * 2
pure doubled
Evidence: [EvBinds{}]}
AbsBinds [] []
{Exports: [greet <= greet
wrap: <>]
Exported types: greet :: Maybe String -> String
[LclId]
Binds: greet name
= case name of
Just{EvBinds{}} n -> "hello, " ++ n
Nothing{EvBinds{}} -> "hello"
Evidence: [EvBinds{}]}
AbsBinds [] []
{Exports: [|+| <= |+|
wrap: <>]
Exported types: (|+|) :: Int -> Int -> Int
[LclId]
Binds: x |+| y = x + y * 2
Evidence: [EvBinds{}]}
AbsBinds [] []
{Exports: [total <= total
wrap: <>]
Exported types: total :: [Int] -> Int
[LclId]
Binds: total xs
= sum @[] $dFoldable @Int $dNum (map @Int @Int (|+| 1) xs)
Evidence: [EvBinds{}]}What the renamer hands on
HsGroup GhcRn: the same AST types as before, at the next phase index. Every
RdrName is now a Name; operator applications are correctly grouped; overloaded
constructs have been expanded; declarations arrive in dependency order.
What is not done: nothing has been type-checked. describe x = "value: " ++ show x
has been fully resolved, and GHC still has no idea whether x has a Show
instance. That question, and roughly eight hundred Notes’ worth of machinery for
answering it, is the next chapter.
Reading the source yourself
GHC/Types/Name/Reader.hsfirst, forRdrName,GlobalRdrEnvandGlobalRdrElt. The renamer is mostly operations on these; the rest reads far more easily once the data types are familiar.GHC/Rename/Env.hs: lookup, and where ambiguity and shadowing are actually decided.GHC/Rename/Expr.hs: start at the Note quoted above, then readrnExpr. The expansion machinery is the least obvious thing in the phase.
A good first contribution here is an error message. The renamer produces many of GHC’s most user-visible diagnostics (out-of-scope names, ambiguous occurrences, unused imports), and improving one is self-contained, testable with a single source file, and cannot break code generation.