Skip to content

Native tsc: interface-only string index signatures no longer simplify under generic indexed access (regressed in #65059) #65127

Open

Description

🔎 Search Terms

isStringIndexSignatureOnlyType, indexed access, string index signature, Dict[K], conditional type simplification, #65059

🕗 Version & Regression Information

⏯ Playground Link

N/A (native vs classic divergence)

💻 Code

interface Dict { [x: string]: number }

type AsNumber<T> = T extends number ? T : never;

function takesNumber<K extends string>(x: AsNumber<Dict[K]>): number {
    return x;
}

function callIt<K extends string>(x: Dict[K]) {
    return takesNumber(x); // should be OK
}
tsc --strict --noEmit a.ts

Control: replacing Dict with type Anon = { [x: string]: number } still works on native.

🙁 Actual behavior

Native tsc @ 042e0fb9574:

a.ts(8,24): error TS2345: Argument of type 'Dict[K]' is not assignable to parameter of type 'AsNumber<Dict[K]>'.
  Type 'number' is not assignable to type 'AsNumber<Dict[K]>'.

Dict[K] is not simplified to number, so the conditional AsNumber<…> stays deferred.

🙂 Expected behavior

Classic 6.0.3: no errors. An interface that is only a string index signature should still be treated as string-index-signature-only for indexed-access simplification, same as an anonymous object type.

Additional information about the issue

#65059 changed:

// before
t.flags&TypeFlagsObject != 0 && !c.isGenericMappedType(t) && …

// after
t.flags&TypeFlagsObject != 0 && t.objectFlags&(ObjectFlagsClassOrInterface|ObjectFlagsReference) == 0 && !c.isGenericMappedType(t) && …

That exclusion was needed to avoid circularity while members are still resolving, but applying it to fully resolved interfaces/classes that truly have only a string index signature breaks getIndexedAccessTypeOrUndefined's simplification path:

if c.isStringIndexSignatureOnlyType(objectType) && … {
    indexType = c.stringType
}

Suggested direction: gate the exclusion on unresolved / currently-resolving types (e.g. MembersResolved == 0 or resolvingObjectTypeStack), not on ClassOrInterface/Reference forever.

Activity

  1. timurzvural commented on Nov 3, 2026

    @timurzvural
    Author

    Same native≠classic failure for a class that is only a string index signature (same AsNumber<Dict[K]> pattern):

    class Dict { [x: string]: number }
    type AsNumber<T> = T extends number ? T : never;
    function takes<K extends string>(x: AsNumber<Dict[K]>): number { return x; }
    function callIt<K extends string>(x: Dict[K]) { return takes(x); }

    Native: TS2345. Classic 6.0.3: OK. Fits the same ObjectFlagsClassOrInterface exclusion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScript

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions