Skip to content

Rule let-matching logic fails to extend in-scope set correctly

Note [Matching lets] in GHC.Core.Rules claims the following:

We use GHC.Core.Subst.substBind to freshen the binding, using an in-scope set that is the original in-scope variables plus the rs_bndrs (currently floated let-bindings). So in (a) above we'll freshen the v binding; in (b) above we'll freshen the second v binding.

However, this isn't what match actually does. Specifically, it fails to extend the in-scope set of the substitution given to substBind with rs_bndrs:

match renv subst e1 (Let bind e2) mco
  | -- pprTrace "match:Let" (vcat [ppr bind, ppr $ okToFloat (rv_lcl renv) (bindFreeVars bind)]) $
    not (isJoinBind bind) -- can't float join point out of argument position
  , okToFloat (rv_lcl renv) (bindFreeVars bind) -- See Note [Matching lets]
  = match (renv { rv_fltR = flt_subst'
                , rv_lcl  = rv_lcl renv `extendRnInScopeSetList` new_bndrs })
                -- We are floating the let-binding out, as if it had enclosed
                -- the entire target from Day 1.  So we must add its binders to
                -- the in-scope set (#20200)
          (subst { rs_binds = rs_binds subst . Let bind'
                 , rs_bndrs = new_bndrs ++ rs_bndrs subst })
          e1 e2 mco
  | otherwise
  = Nothing
  where
    (flt_subst', bind') = substBind (rv_fltR renv) bind
    new_bndrs           = bindersOf bind'

Now, one might be tempted to think that this is okay; afterall, match does extend rv_fltR of the RuleMatchEnv. However, match_exprs does not propagate RuleMatchEnv across matched arguments; only the RuleSubst will be carried over when we match the next argument.

I don't yet know why we haven't observed fallout from this master, but this seems like a bug which resulted in Core Lint failures while building Data.Binary.Class on the 9.2 branch (see !7602 (merged)).

Edited by Ben Gamari
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information