Repository navigation
Spell Selector without the self-reference its base type holds #348
Description
Selector no longer checks on the TypeScript 7.1 nightlies, since the nightly of 2026-09-29.
$ npm i -D typescript@next
$ tsc --noEmit --strict --skipLibCheck app.ts
app.ts(9,20): error TS5115: Instantiations of the following types appear infinitely circular: 'Container', 'Diff'.The error is reported where Selector is used, not in the declaration, so it depends on what the consumer does with the type rather than on the package alone.
The spelling is the workaround of #232: type Selector = _Selector<Selector> over interface _Selector<S> extends Container<string, Diff<Node, S>>. No released TypeScript is affected — 4.0.3, 4.9.5, 5.0.4, 5.9.3 and 7.0.2 all check it — and the nightlies of 2026-09-28 and earlier do too.
Here is what each spelling costs, measured with tsc --noEmit --strict on a consumer that narrows a selector's children by type and reads them:
spelling of Selector |
4.0.3 | 5.9.3 | 7.0.2 | 7.1 nightly |
|---|---|---|---|---|
_Selector<S> + type Selector = _Selector<Selector>, as shipped |
ok | ok | ok | TS5115 |
interface Selector extends Container<string, Exclude<Node, Selector>> |
TS2310 | TS2310 | TS2310 | ok |
interface Selector extends Container<string, Diff<Node, Selector>> |
TS2310 | TS2310 | TS2310 | ok |
interface Selector extends Container<string, Diff<Node, NodeTypes["selector"]>> |
TS2310 | TS2310 | TS2310 | ok |
type SelectorChild = Diff<Node, Selector>, Selector extends Container<string, SelectorChild> |
TS2310 | TS2310 | TS2310 | ok |
interface Selector<S = Selector> extends Container<string, Diff<Node, S>> |
errors | errors | errors | errors |
interface Selector extends Container<string, Node> |
ok | ok | ok | ok |
A selector that says its children are the nodes that are not selectors has to name the node union in its own base type, and that union holds Selector — which is the cycle, whichever way it is spelled. Adding a default argument to _Selector changes nothing, since the alias still names itself.
So there seem to be three ways out, and I would rather ask than pick:
- drop the exclusion, so a selector's children are
Node[]. The parser never puts a selector inside a selector, butselector.nodeswas the one place in the tree that said so. - declare
nodesonSelectoritself rather than throughContainer, if that keeps the base type free of the node union. - leave it, and let the consumers on the nightly carry a patch until 7.1 ships.
We hit this in stylelint-stylistic, which pins typescript@next; the workaround there is a patchedDependencies entry that would be glad to drop once any of the lands.
Activity
firefoxic commentedon Oct 18, 2026
I'm ready to send a PR with the latest fix, which is
okfor all versions.