Free type variables in a static form are (wrongly) rejected
<!-- ══════════════════════════════════════════════════════════════════
Draft metadata (for Simon — strip before filing)
Covers GlaHiCo finding f2 of snippet 6148. Finding f1 was DROPPED —
see below.
Suggested labels: ~"T::bug" ~renamer ~StaticPointers
Suggested milestone: %9.14.2 (the warning ships there). The error
side lives on master (4df0adf631) — arguably
also/instead %10.0.1; mention on the 9.14.2
status thread either way.
Related: #26718 (the warning's tracking ticket — maybe
file as a comment there instead of a new
ticket?), #26556 (proposal 732 umbrella),
!15292 (source MR, still open), !16163
(backport MR that landed it on ghc-9.14)
Commits: branch 1ee3466bf0db (cherry-pick of 6b7d6287c6ff
from wip/T26178 via !15292 — NOT on master);
master has the full 9.16 change 4df0adf631
where the same defect is a hard error.
Reproducer: tickets/f2/ (run.sh; verified 2026-08-07,
x86_64-linux):
- ghc-9.14.1 (ghcup): silent success
- 9.14.1.20260728 bindist = branch commit
eddff3ab50 (includes 1ee3466bf): warning
GHC-82529 naming tyvar 'a', exit 0
- master build 10.1.20260710 (d415f38a75,
includes 4df0adf631): error GHC-88431, exit 1
f1 dropped (was: "warning also fires for lambda-bound vars,
breaking RnStaticPointersFail01/03"): refuted empirically. TcRn
warnings are suppressed when the module has errors (checked with
ghc-9.14.1: an -Wx-partial warning disappears when the module also
has a type error), and every not-let-bound offender also triggers
the hard error GHC-88431, so the contradictory warning never
reaches the user: the snapshot compiler shows only the old error
for `f x = static x`, no warning. Branch CI agrees: pipeline
129003 on 39bc70bd67 ran the full testsuite (x86_64-windows job:
10892 tests, 0 unexpected failures; i386 job failed only T27131),
so RnStaticPointersFail01/03 pass. The bad_fvs over-approximation
for lambda-bound vars exists in the source but has no observable
effect on the branch; the suggested fix below removes it anyway.
═══════════════════════════════════════════════════════════════════ -->
*This ticket reports a possible bug found by an LLM-based code review (details under "Provenance" below). I, @sjakobi, have checked it for plausibility, but not verified it in depth. @simonpj subsequently rewrote it.
## Summary
Following [GHC proposal 732: simplify static forms](https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0732-simplify-static.rst) GHC is supposed to check all the free **term variables** of a static form are top-level bound.
But the proposal explicitly allows free **type variables** (see Section 5.2).
The proposal was implemented in !14994. But alas the implemention now rejects free type variables. Repro case below.
Also:
* Less importantly, this also affects the deprecation warning in the GHC 9.14 branch, which was added in !15292
* There are bogus references to `checkClosedInStaticForm` in the code; they should be removed.
## Reproducer
```haskell
{-# LANGUAGE StaticPointers, ScopedTypeVariables #-}
module Repro where
import Data.Typeable
import GHC.StaticPtr
f :: forall a. Typeable a => StaticPtr (a -> a)
f = static (id :: a -> a)
```
This is `TcStaticPointers02.f2` with an inline signature. Observed
behaviour (x86_64-linux):
* **GHC 9.14.1**: compiles silently.
* **ghc-9.14 branch** (9.14.1.20260728 bindist, commit eddff3ab50,
containing 1ee3466bf0db): compiles, but warns —
```
Repro.hs:8:5: warning: [GHC-82529]
From GHC 9.16, static forms cannot mention nested let-bound variables
Offending variables: a
Solution: move these free variables to top level
```
`a` is not a let-bound variable and cannot be moved to top level;
the warning has no flag, so `-Werror` users cannot get a clean
build without changing (correct) code, e.g. dropping the inline
annotation.
* **master** (10.1.20260710 build, containing 4df0adf631 "Simplify
the treatment of static forms", where the check is the real 9.16
error): the program is rejected —
```
Repro.hs:8:5: error: [GHC-88431]
‘a’ is used in a static form but it is not defined at top level
```
Proposal 732 restricts the free *term* variables of a static form;
a type variable in an annotation should not make it ill-formed
(without the annotation the same definition is accepted on master).
## Cure
Because of required type arguments, we can't tell type variables from term variables in
the renamer.
So probably:
* The renamer should put the free varaibles in the extension field of `HsStatic`
* The typechecker should check that there are no term variables among them.
## Provenance
Originally reported by [GlaHiCo](https://gitlab.haskell.org/sjakobi/GlaHiCo), an
LLM-based code-review pipeline, while reviewing the 9.14.2-rc1
backports. The published review of 1ee3466bf0db (cherry-pick
of master 6b7d6287c6ff from !15292, landed via !16163) is at
https://gitlab.haskell.org/sjakobi/GlaHiCo/-/snippets/6148 (finding f2).
The first draft of this ticket was also written by an LLM agent.
**Subsequently edited by @simonpj to kill off bad suggestions (e.g. using the OccName to tell type variables)
issue