[Git][ghc/ghc][master] Seed the simplifier's in-scope set for open expressions
Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC Commits: 4671c126 by Sebastian Graf at 2026-06-17T05:35:55-04:00 Seed the simplifier's in-scope set for open expressions simplifyExpr simplifies expressions typed at the GHCi prompt and the results of Template Haskell splices. Such an expression may be open: at a GHCi debugger breakpoint its free variables include RuntimeUnk skolems standing for as-yet-unknown types. The simplifier began with an in-scope set holding only the wildcard binder, so when it instantiated the unsafeCoerce# wrapper that GHCi builds around a result, it formed a substitution whose range mentioned a free skolem that was not in scope. That breaks the substitution invariant and, in a compiler built with assertions, trips substTy's sanity check. Seed the initial in-scope set with the free variables of the expression. For a closed expression this adds nothing. See Note [Seed the in-scope set for open expressions]. Fixes #17833 and its duplicate #21118. - - - - - 3 changed files: - + changelog.d/T17833 - compiler/GHC/Core/Opt/Simplify.hs - testsuite/tests/ghci.debugger/scripts/all.T Changes: ===================================== changelog.d/T17833 ===================================== @@ -0,0 +1,11 @@ +section: compiler +synopsis: Fix a Simplifier panic on open expressions (e.g. in the GHCi debugger) +issues: #17833 #21118 +mrs: !16187 +description: { + ``simplifyExpr`` now seeds the Simplifier's in-scope set with the free + variables of the expression being simplified, restoring the invariant that the + in-scope set contains all of an expression's free variables. This fixes a panic + (a failed ``substTy`` assertion) when an open expression reaches the Simplifier, + as can happen for expressions entered in the GHCi debugger. +} ===================================== compiler/GHC/Core/Opt/Simplify.hs ===================================== @@ -14,6 +14,7 @@ import GHC.Core.Rules import GHC.Core.Ppr ( pprCoreBindings, pprCoreExpr ) import GHC.Core.Opt.OccurAnal ( occurAnalysePgm, occurAnalyseExpr ) import GHC.Core.Stats ( coreBindsSize, coreBindsStats, exprSize ) +import GHC.Core.FVs ( exprFreeVars ) import GHC.Core.Utils ( mkTicks, stripTicksTop ) import GHC.Core.Lint ( LintPassResultConfig, dumpPassResult, lintPassResult ) import GHC.Core.Opt.Simplify.Iteration ( simplTopBinds, simplExpr, simplImpRules ) @@ -74,7 +75,10 @@ simplifyExpr logger euc opts expr ; let fam_envs = ( eps_fam_inst_env eps , extendFamInstEnvList emptyFamInstEnv $ se_fam_inst opts ) - simpl_env = mkSimplEnv (se_mode opts) fam_envs + -- See Note [Seed the in-scope set for open expressions] + simpl_env = setInScopeSet base_env + (getInScope base_env `extendInScopeSetSet` exprFreeVars expr) + base_env = mkSimplEnv (se_mode opts) fam_envs top_env_cfg = se_top_env_cfg opts read_eps_rules = eps_rule_base <$> eucEPS euc read_ruleenv = updExternalPackageRules emptyRuleEnv <$> read_eps_rules @@ -94,6 +98,49 @@ simplifyExpr logger euc opts expr ; return expr' } +{- Note [Seed the in-scope set for open expressions] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +An invariant of the Simplifier is that the in-scope set contains all the free +variables of the expression being simplified. Why? So that the Simplifier +never invents a "fresh" binder that accidentally captures one of those free +variables. + +'simplifyExpr' establishes that invariant by initialising the in-scope set with +the free variables of the expression it is given. For a closed expression this +adds nothing. + +But how can 'simplifyExpr' be handed an /open/ expression in the first place? +The whole-module pipeline never does so: 'simplifyPgm' only ever simplifies +closed bindings. The one caller that can is 'hscCompileCoreExpr' (in +GHC.Driver.Main.Passes), which compiles a single Core expression, and is itself +reached from just two places: + + * GHCi, for an expression typed at the prompt, and in particular in the + debugger (see below); + * Template Haskell, when running a splice (see GHC.Tc.Gen.Splice). + +So this is never on the critical path for mainstream compilation. + +Here is how the debugger hands us an open expression (test break006). We are +stopped at a breakpoint in + mymap f (x:xs) = f x : ... +where the debugger knows that 'x :: Int' but not yet the result type of 'f'. So +it invents a RuntimeUnk skolem 'a' and gives 'f :: Int -> a'. The user then +types + let y = f x +GHCi type-checks that and, to hand the result back to the interpreter (see +GHC.Tc.Module.tcGhciStmts), wraps it as + returnIO @[Any] [unsafeCoerce# @LiftedRep @LiftedRep @a @Any y] +in which the skolem 'a' is free. + +When the Simplifier instantiates that 'unsafeCoerce#', it builds a substitution +mapping unsafeCoerce#'s quantified type variables to (LiftedRep, LiftedRep, a, +Any), whose range therefore mentions 'a'. The in-scope set of a substitution +must contain the free variables of its range (see Note [The substitution +invariant] in GHC.Core.TyCo.Subst). Without the seeding above that invariant is +broken, and in a compiler built with assertions substTy's sanity check fails +(#17833, and its duplicate #21118). -} + simplExprGently :: SimplEnv -> CoreExpr -> SimplM CoreExpr -- ^ Simplifies an expression by doing occurrence analysis, then simplification, -- and repeating (twice currently), because one pass alone leaves tons of crud. ===================================== testsuite/tests/ghci.debugger/scripts/all.T ===================================== @@ -52,8 +52,7 @@ test('break002', extra_files(['../Test2.hs']), ghci_script, ['break002.script']) test('break003', extra_files(['../Test3.hs']), ghci_script, ['break003.script']) test('break005', extra_files(['../QSort.hs']), ghci_script, ['break005.script']) test('break006', - [ when(compiler_debugged(), expect_broken(17833)), - extra_files(['../Test3.hs'])], + [extra_files(['../Test3.hs'])], ghci_script, ['break006.script']) test('break007', extra_files(['Break007.hs']), ghci_script, ['break007.script']) View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/4671c126599b791c231abf0946662031... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/4671c126599b791c231abf0946662031... You're receiving this email because of your account on gitlab.haskell.org.
participants (1)
-
Marge Bot (@marge-bot)