Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
danvk opened this issue Apr 9, 2022 · 2 comments · Fixed by #48699
Closed

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

danvk opened this issue Apr 9, 2022 · 2 comments · Fixed by #48699
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@danvk
Copy link
Contributor

danvk commented Apr 9, 2022

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.

@RyanCavanaugh
Copy link
Member

Bisect > #47889

@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Apr 11, 2022
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 4.7.1 milestone Apr 11, 2022
@ahejlsberg
Copy link
Member

ahejlsberg commented Apr 13, 2022

As explained here, we can't generally simplify generic mapped types to instantiations of their template type when an as clause is present. That's what was fixed in #47889. However, when an as clause simply filters the property names (i.e. when it is a conditional type in which one branch is the iteration type variable and the other is never, or when it is an intersection that includes the iteration type variable), we could potentially still permit the simplification. Same is true if the template type doesn't reference the iteration type variable (as is the case here), though we're not equipped to consistently detect that.

I'll try to put together a PR that permits the filtering case as I think that's fairly common.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants