Repository navigation
Type of F<T>[keyof F<T>] becomes unknown in TS 4.7.0-beta #48626
Copy link
Copy link
Closed
Labels
BugA bug in TypeScriptA bug in TypeScriptFix AvailableA PR has been opened for this issueA PR has been opened for this issue
Milestone
Description
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.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
BugA bug in TypeScriptA bug in TypeScriptFix AvailableA PR has been opened for this issueA PR has been opened for this issue
Activity
RyanCavanaugh commentedon Apr 11, 2022
Bisect > #47889
ahejlsberg commentedon Apr 13, 2022
As explained here, we can't generally simplify generic mapped types to instantiations of their template type when an
asclause is present. That's what was fixed in #47889. However, when anasclause 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 isnever, 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.