We expand `do`-blocks before typechecking it, by re-using the existing `XXExprGhcRns` and `RebindableSyntax` machinery.
This is very similar to:
1. Expansions done in `GHC.Rename.Expr.rnHsIf` for expanding `HsIf`; and
2. `desugarRecordUpd` in `GHC.Tc.Gen.Expr.tcExpr` for expanding `RecordUpd`
See Note [Handling overloaded and rebindable constructs] in GHC.Rename.Expr
To disabmiguate desugaring (`HsExpr GhcTc -> Core.Expr`) we use the phrase expansion
(`HsExpr GhcRn -> HsExpr GhcRn`)
This expansion is done right before typechecking and after renaming
See Part 2. of Note [Doing XXExprGhcRn in the Renamer vs Typechecker] in `GHC.Rename.Expr`
Historical note START
In previous versions of GHC, the `do`-notation wasn't expanded before typechecking,
instead the typechecker would operate directly on the original.
Why? because it ensured that type error messages were explained in terms of
what the programmer has written. In practice, however, this didn't work very well:
* Attempts to typecheck the original source code turned out to be buggy, and virtually impossible
to fix (#14963, #15598, #21206 and others)
* The typechecker expected the `>>=` operator to have a type that matches
`m _ -> (_ -> m _) -> m _` for some `m`. With `RebindableSyntax` or
`QualifiedDo` the `>>=` operator might not have the
standard type. It might have a type like
(>>=) :: Wombat m => m a1 a2 b -> (b -> m a2 a3 c) -> m a1 a3 c
Typechecking the term `(>>=) e1 (\x -> e2)` deals with all of this automatically.
* With `ImpredicativeTypes` the programmer will expect Quick Look to instantiate
the quantifiers impredicatively (#18324). Again, that happens automatically if
you typecheck the expanded expression.
Historical note END
Do Expansions Equationally
We have the following schema for expanding `do`-statements.
They capture the essence of statement expansions as implemented in `expand_do_stmts`
DO【 _ 】 maps a sequence of do statements and recursively converts them into expressions
(1) DO【 s; ss 】 = ‹ExpansionStmt s›((>>) s (‹PopErrCtxt›DO【 ss 】))
(2) DO【 p <- e; ss 】 = if p is irrefutable
then ‹ExpansionStmt (p <- e)›
(>>=) s (‹PopExprCtxt›(\ p -> DO【 ss 】))
else ‹ExpansionStmt (p <- e)›
(>>=) s (‹PopExprCtxt›(\case p -> DO【 ss 】
_ -> fail "pattern p failure"))
(3) DO【 let x = e; ss 】
= ‹ExpansionStmt (let x = e)› (let x = e in (‹PopErrCtxt›DO【 ss 】))
(4) DO【 rec ss; sss 】
= (>>=) e (\vars -> ‹PopErrCtxt›DO【 sss 】))
where (vars, e) = RECDO【 ss 】
(5) DO【 s 】 = s
RECDO【 _ 】 maps a sequence of recursively dependent monadic statements and converts it into an expression paired
with the variables that the rec finds a fix point of.
(6) RECDO【 ss 】 = (vars, mfix (\~vars -> (>>=) (DO【 ss 】) (return vars)))
where vars are all the variables free in ss
For a concrete example, consider a `do`-block written by the user
f = {l0} do {l1} {pl}p <- {l1'} e1
{l2} g p
{l3} return {l3'} p
The expanded version (performed by `expand_do_stmts`) looks like:
f = {g1} (>>=) ({l1'} e1) (\ {pl}p ->
{g2} (>>) ({l2} g p)
({l3} return p))
The {l1} etc are location/source span information stored in the AST by the parser,
{g1} are compiler generated source spans.
The 3 non-obvious points to consider are:
1. Wrap the expression with a `fail` block if the pattern match is not irrefutable.
See Part 1. below
2. Generate appropriate warnings for discarded results in a body statement
eg. say `do { .. ; (g p :: m Int) ; ... }`
See Part 2. below
3. Generating appropriate type error messages which blame the correct source spans
See Part 3. below
Part 1. Expanding Patterns Bindings
If `p` is a failable pattern---checked by `GHC.Tc.Gen.Pat.isIrrefutableHsPatRnTcM`---
we need to wrap it with a `fail`-block. See Equation (2) above.
The expansion of the `do`-block
do { Just p <- e1; e2 }
(ignoring the location information) will be
(>>=) (e1)
(\case -- anonymous continuation lambda
Just p -> e2
_ -> fail "failable pattern p at location")
The `fail`-block wrapping is done by `GHC.Tc.Gen.Do.mk_failable_expr`.
* Note the explicit call to `fail`, in the monad of the `do`-block. Part of the specification
of do-notation is that if the pattern match fails, we fail in the monad, *not* just crash
at runtime.
* According to the language specification, when the pattern is irrefutable,
we should not add the `fail` alternative. This is important because
the occurrence of `fail` means that the typechecker will generate a `MonadFail` constraint,
and irrefutable patterns shouldn't need a fail alternative.
* _Wrinkel 1_: Note that pattern synonyms count as refutable during type checking,
(see `isIrrefutableHsPat`). They will hence generate a
`MonadFail` constraint and they will always be wrapped in a `fail`able-block.
Consider a patten synonym declaration (testcase T24552):
pattern MyJust :: a -> Maybe a
pattern MyJust x <- Just x where MyJust = Just
and a `do`-block with the following bind and return statement
do { MyJust x <- [MyNothing, MyJust ()]
; return x }
The `do`-expansion will generate the expansion
(>>=) ([MyNothing, MyJust ()])
(\case MyJust x -> return x -- (1)
_ -> fail "failable pattern .. " -- (2)
)
This code (specifically the `match` spanning lines (1) and (2)) is a compiler generated code;
the associated `Origin` in tagged `Generated`
The alternative statements will thus be ignored by the pattern match check (c.f. `isMatchContextPmChecked`).
This ensures we do not generate spurious redundant-pattern-match warnings due to the line (2) above.
See Note [Generated code and pattern-match checking]
See Note [Long-distance information in matchWrapper]
* _Wrinkle 2_: The call to `fail` will give rise to a `MonadFail` constraint. What `CtOrigin` do we
attach to that constraint? When the `MonadFail` constraint can't be solved, it'll show up in error
messages and it needs to be a good location. Ideally, it should identify the
pattern `p`. Hence, we wrap the `fail` alternative expression with a `ExpandedPat`
that tags the fail expression with the failable pattern. (See testcase MonadFailErrors.hs)
Part 2. Generate warnings for discarded body statement results
If the `do`-blocks' body statement is an expression that returns a
value that is not of type `()`, we need to warn the user about discarded
the value when `-Wunused-binds` flag is turned on. (See testcase T3263-2.hs)
For example the `do`-block
do { e1; e2 } -- where, e1 :: m Int
expands to
(>>) e1 e2
* If `e1` returns a non-() value we want to emit a warning, telling the user that they
are discarding the value returned by e1. This is done by `HsToCore.dsExpr` in the `HsApp`
with a call to `HsToCore.warnUnusedBindValue`.
* The decision to trigger the warning is: if the function is a compiler generated `(>>)`,
and its first argument `e1` has a non-() type
Part 3. Blaming Offending Source Code and Generating Appropriate Error Messages
To ensure we correctly track source of the offending user written source code,
in this case the `do`-statement, we need to keep track of
which source statement's expansion the typechecker is currently typechecking.
For this purpose we use the `XXExprGhcRn.ExpansionRn`.
It stores the original statement (with location) and the expanded expression
A. Expanding Body Statements
For example, the `do`-block
do { e1; e2; e3 }
expands (ignoring the location info) to
‹ExpandedThingRn do { e1; e2; e3 }› -- Original Do Expression
Expanded Do Expression
(‹ExpandedThingRn e1› -- Original Statement
({(>>) e1} -- Expanded Expression
‹PopErrCtxt› (‹ExpandedThingRn e2›
({(>>) e2}
‹PopErrCtxt› (‹ExpandedThingRn e3› {e3})))))
* Whenever the typechecker steps through an `ExpandedThingRn`,
we push the original statement in the error context, set the error location to the
location of the statement, and then typecheck the expanded expression.
This is similar to vanilla `XXExprGhcRn` and rebindable syntax
See Note [Rebindable syntax and XXExprGhcRn] in `GHC.Hs.Expr`.
* Recall, that when a source function argument fails to typecheck,
we print an error message like "In the second argument of the function f..".
However, `(>>)` is generated thus, we don't want to display that to the user; it would be confusing.
But also, we do not want to completely ignore it as we do want to keep the error blame carets
as precise as possible, and not just blame the complete `do`-block.
Thus, when we typecheck the application `(>>) e1`, we push the "In the stmt of do block e1" with
the source location of `e1` in the error context stack as we walk inside an `ExpandedThingRn`.
See also Note [splitHsApps].
* After the expanded expression of a `do`-statement is typechecked
and before moving to the next statement of the `do`-block, we need to first pop the top
of the error context stack which contains the error message for
the previous statement: eg. "In the stmt of a do block: e1".
This is explicitly encoded in the expansion expression using
the `XXExprGhcRn.PopErrCtxt`. Whenever `GHC.Tc.Gen.Expr.tcExpr` (via `GHC.Tc.Gen.tcXExpr`)
sees a `PopErrCtxt` it calls `GHC.Tc.Utils.Monad.popErrCtxt` to pop of the top of error context stack.
See ‹PopErrCtxt› in the example above.
Without this popping business for error context stack,
if there is a type error in `e2`, we would get a spurious and confusing error message
which mentions "In the stmt of a do block e1" along with the message
"In the stmt of a do block e2".
B. Expanding Bind Statements
A `do`-block with a bind statement:
do { p <- e1; e2 }
expands (ignoring the location information) to
‹ExpandedThingRn do{ p <- e1; e2 }› -- Original Do Expression
(‹ExpandedThingRn (p <- e1)› -- Original Statement
(((>>=) e1) -- Expanded Expression
‹PopErrCtxt› ((\ p -> ‹ExpandedThingRn (e2)› e2)))
)
However, the expansion lambda `(\p -> e2)` is special as it is generated from a `do`-stmt expansion
and if a type checker error occurs in the pattern `p` (which is source generated), we need to say
"in a pattern binding in a do block" and not "in the pattern of a lambda" (cf. Typeable1.hs).
We hence use a tag `GenReason` in `Ghc.Tc.Origin`. When typechecking a `HsLam` in `Tc.Gen.Expr.tcExpr`
the `match_ctxt` is set to a `StmtCtxt` if `GenOrigin` is a `DoExpansionOrigin`.