Skip to content

Type of F<T>[keyof F<T>] becomes unknown in TS 4.7.0-beta #48626

Closed
@danvk

Description

@danvk

Bug Report

I ran into this issue while testing my code with the new TS 4.7 beta. I didn't see anything in the breaking changes that looked related, so here's an issue!

🔎 Search Terms

  • 4.7.0-beta

🕗 Version & Regression Information

  • This changed between versions 4.6.2 and 4.7.0-beta

⏯ Playground Link

Playground Link: 4.7.0-dev showing the error and 4.6.2 not showing an error.

💻 Code

interface Bounds {
  min: number;
  max: number;
}

/**
 * A version of T where number-valued properties become Bounds-valued properties and all other
 * properties are dropped, e.g. NumericBoundsOf<{a: number, b: string}> = {a: Bounds}.
 */
type NumericBoundsOf<T> = {
  [K in keyof T as T[K] extends number | undefined ? K : never]: Bounds;
}

// Works as intended:
type X = NumericBoundsOf<{a: number; b: string}>;
//   ^? type X = { a: Bounds; }

function validate<T extends object>(obj: T, bounds: NumericBoundsOf<T>) {
  for (const [key, val] of Object.entries(obj)) {
    const boundsForKey = bounds[key as keyof NumericBoundsOf<T>];
    if (boundsForKey) {
      const {min, max} = boundsForKey;
      if (min > val || max < val) return false;
    }
  }
  return true;
}
Output
"use strict";
function validate(obj, bounds) {
    for (const [key, val] of Object.entries(obj)) {
        const boundsForKey = bounds[key];
        if (boundsForKey) {
            const { min, max } = boundsForKey;
            if (min > val || max < val)
                return false;
        }
    }
    return true;
}
Compiler Options
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2017",
    "jsx": "react",
    "module": "ESNext",
    "moduleResolution": "node"
  }
}

🙁 Actual behavior

This code fails to type check in TypeScript 4.7.0-beta:

function validate<T extends object>(obj: T, bounds: NumericBoundsOf<T>) {
  for (const [key, val] of Object.entries(obj)) {
    const boundsForKey = bounds[key as keyof NumericBoundsOf<T>];
    if (boundsForKey) {
      const {min, max} = boundsForKey;
      //     ~~~      Property 'min' does not exist on type 'unknown'. ts(2339)
      //          ~~~ Property 'max' does not exist on type 'unknown'. ts(2339)
      if (min > val || max < val) {
        return false;
      }
    }
  }
  return true;
}

It does pass the type checker in TS 4.6.2, as I believe it should.

🙂 Expected behavior

boundsForKey should have a type of Bounds and this should pass the type checker (as it does in TS 4.6.2).

The type of boundsForKey is displayed as NumericBoundsOf<T>[keyof NumericBoundsOf<T>] in both versions, but evidently TS 4.7.0-beta resolves this to unknown whereas TS 4.6.2 resolved it to Bounds.

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions