This fixes the case where an inlined comprehension has an internal cellvar (i.e. it creates lambdas that close over the comprehension iteration variable), but the same name is also used a fast-local in the outer scope, and is read but never written within the body of the function (so e.g. an argument).
In code like this:
def f(x):
items = [lambda: x for x in range(2)]
return x
because x is a cell inside the comprehension, after inlining it would appear in u_cellvars for f, and thus the compilation of f would insert a MAKE_CELL for x at the start of the function. But we would still compile the non-comprehension part of the function treating x as a fast-local (emitting LOAD_FAST for it), so f would return a cell object containing the value of x, instead of returning x directly.
Similar cases were already tested, but only with a write (compiled as STORE_FAST) to x before it was referenced, which stored the value directly and overwrote the wrongly-created cell.
One approach to fix this would be to track which names are cells only inside an inlined comprehension, and prevent the compiler from emitting MAKE_CELL for those names (the inlined comprehension will itself emit MAKE_CELL where it should, within the comprehension's isolated scope.) But this fix would add additional complexity to the compiler.
A simpler fix (implemented here) is to ensure that we always treat such a variable as a cell throughout the outer function too (emitting LOAD_DEREF and STORE_DEREF for it). This maintains clearer semantics for co_cellvars (if a name is in there, it is a cell in the main function) and keeps the compiler simpler. The downside is that cells are slightly slower than fast locals, but comprehensions creating lambdas that close over the iteration variable are rare in real code (they don't behave as people usually want them to, given that all the created lambda functions share the same final value in their closure), so I don't think performance is a concern in this edge case.
Fixes #104357