A name the emitted code reads or declares can be one a C# name holds #397
Description
Activity
One slice of this is in #484: a switch's subject is bound to
$snow, in both switch lowerings (the if-chain of a pattern switch and the switch expression's arrow).#484 declares a section's and an arm's variables in the switch's own scope, which made the old name reachable:
return 1 switch { 1 => int.TryParse("2", out var _s) ? _s : 0, _ => 0 };waslet _s; const _s = 1, a module that did not parse. Two conformance cases pin it.The rest stays here, the globals included... #484 briefly escaped a local called
undefined,NaNorInfinityand then withdrew it, since this issue moves the emitted code instead of the local.Two things #399 (merged as 29c4286) leaves for this issue, so that whoever does it removes them rather than keeping them beside the real fix.
- The constructor's
propsis fixed only where it collides.TypeScriptEmitter.ConfigParameternames the config object$propswhen the constructor already bindsprops, as a parameter or as a local of its body, andpropsotherwise. What this issue wants,$propsalways, is the structural form: it needs no condition and no scan of the constructor. When it lands,ConfigParametergoes, and so does thepropsentry inLocalFunctionName.EmittedNames. The component pins will move in every constructor, which is why 🐛 fix: a name the transpiler changes lands on no name its scope holds #399 did not take it at its seventh round. - The globals a local function yields to are a list that will have nothing left to protect.
LocalFunctionName.EmittedNamesholdsconsole,parseInt,parseFloat,isNaN,crypto,setTimeout, the four URI functions andundefined. A test (LocalFunctionNameTests) reads the compiler's source for every global it emits and fails on one missing from that list. Once the emitted code reaches them throughglobalThisandvoid 0, no local function can shadow them, so the list empties. The same scan then becomes this issue's guard, turned around: the emitted code reads no global by its bare name.
#484 withdrew its escape of locals named
undefined,NaNandInfinity(31aa0b8) so that this issue keeps one mechanism for the collision. The local keeps its spelling and the emitted code moves.- The constructor's
- added a commit that references this issue
on Sep 27, 2026 - added 4 commits that reference this issue
on Oct 8, 2026
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
The translation reads some names it never declares, and declares some that a C# name can reach. C# sees neither, so a program C# accepts can mean something else on the web.
Measured
Conformance probes executed on both sides on 2026-09-24 at d4fdd6c, except the last, which is the plain-class emitter's output:
int parseInt = 2; return Convert.ToInt32("21") * parseInt;answers 42 in .NET and throws here (parseInt is not a function), becauseConvert.ToInt32over text isparseInt('21', 10).var crypto = "xy";besideGuid.NewGuid()throws the same way (crypto.randomUUID is not a function).int _sum = 10; xs.Sum(v => v + _sum)over{ 1, 2 }answers 23 in .NET and 4 here, with no error:SumStrategynames its accumulator_sum, and the selector's_sumreads it.var key = 10;read in a key selector,new[] { 1, 2, 3, 4 }.GroupBy(x => x % 2 + key).Count(), throwsReferenceError: Cannot access 'key' before initialization: the reduce arrow ofGroupByStrategydeclaresconst keyand the selector reads it.int a = -1;read innew[] { 3, 1, 2 }.OrderBy(x => x * a).First()answers 1 where .NET answers 3, with no error: the comparator ofOrderByStrategytakes(a, b). Both are common names, and a local FUNCTION calledkeyorafails the same way (ReferenceError, TypeError).propsin a constructor with no parameters of its own. The plain-class emitter adds apropsparameter, andlet props = 5beside it does not parse.The free names the lowerings read, from the compiler's source:
console(Console.WriteLine),parseIntandparseFloat(Convert over text),crypto(Guid.NewGuid),encodeURIComponentanddecodeURIComponent(Uri),undefined, and the constructor'sprops.By reading, to measure first:
AverageStrategynames its accumulator_sumtoo, and the two strategies name their other arrows' parameters_x,_aand_b. Copilot's review of #399 names more of them:_idx(List.Remove, which never declares it, #400), and_m,_k,_g,_r,_y,_zin the LINQ lowerings (DistinctStrategy,LinqTableStrategy). A local calledundefinedorconsolewould shadow the same way. A switch statement's_sdid not collide in a constant switch, since that shape does not declare it.#399 makes a local FUNCTION avoid all of these: a name written with an underscore always takes a
$, and the globals are a list that a test derives from the compiler's source. A local keeps its C# spelling, so here it is the emitted code that has to move.What it wants
The emitted code reading no free name a C# name can reach, and declaring none. A synthesized name is
$-prefixed like the IR's own binders ($0,$item), since no C# identifier holds a$. The globals are reached where no local can shadow them (globalThis.parseInt,globalThis.crypto,void 0forundefined), and the constructor's parameter is$props. Each divergence wants a conformance case that fails before and passes after.