Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22895,14 +22895,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return result;
}

function getApparentMappedTypeKeys(nameType: Type, targetType: MappedType) {
const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType));
function getApparentMappedTypeKeys(nameType: Type, mappedType: MappedType) {
const modifiersType = getApparentType(getModifiersTypeFromMappedType(mappedType));
const mappedKeys: Type[] = [];
forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(
modifiersType,
TypeFlags.StringOrNumberLiteralOrUnique,
/*stringsOnly*/ false,
t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(targetType.mapper, getTypeParameterFromMappedType(targetType), t))),
t => void mappedKeys.push(instantiateType(nameType, appendTypeMapping(mappedType.mapper, getTypeParameterFromMappedType(mappedType), t))),
);
return getUnionType(mappedKeys);
}
Expand Down Expand Up @@ -23277,7 +23277,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// allow assignments of index types of identical (or similar enough) mapped types.
// eg, `keyof {[X in keyof A]: Obj[X]}` should be assignable to `keyof {[Y in keyof A]: Tup[Y]}` because both map over the same set of keys (`keyof A`).
// Without this source-side breakdown, a `keyof {[X in keyof A]: Obj[X]}` style type won't be assignable to anything except itself, which is much too strict.
const sourceMappedKeys = nameType && isMappedTypeWithKeyofConstraintDeclaration(mappedType) ? getApparentMappedTypeKeys(nameType, mappedType) : (nameType || getConstraintTypeFromMappedType(mappedType));
let sourceMappedKeys: Type;
if (nameType && isMappedTypeWithKeyofConstraintDeclaration(mappedType)) {
sourceMappedKeys = getApparentMappedTypeKeys(nameType, mappedType);
if (sourceMappedKeys.flags & TypeFlags.Never) {
Comment thread
Andarist marked this conversation as resolved.
Outdated
Comment thread
Andarist marked this conversation as resolved.
Outdated
// modifiers type of mapped type is often `unknown`, `keyof unknown` is `never` and that's assignable to everything
// letting this through is too permissive so we use the apparent type of an index type here instead
sourceMappedKeys = stringNumberSymbolType;
}
}
else {
sourceMappedKeys = nameType || getConstraintTypeFromMappedType(mappedType);
}
if (result = isRelatedTo(sourceMappedKeys, target, RecursionFlags.Source, reportErrors)) {
return result;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
keyRemappingKeyofResult.ts(82,3): error TS2322: Type 'string' is not assignable to type 'Extract<keyof { [P in keyof T as T[P] extends string ? P : never]: any; }, string>'.
keyRemappingKeyofResult.ts(90,3): error TS2322: Type 'string' is not assignable to type 'keyof { [P in keyof T as T[P] extends string ? P : never]: any; }'.
Type 'string' is not assignable to type 'T[P] extends string ? P : never'.


==== keyRemappingKeyofResult.ts (2 errors) ====
const sym = Symbol("")
type Orig = { [k: string]: any, str: any, [sym]: any }

type Okay = Exclude<keyof Orig, never>
// type Okay = string | number | typeof sym

type Remapped = { [K in keyof Orig as {} extends Record<K, any> ? never : K]: any }
/* type Remapped = {
str: any;
[sym]: any;
} */
// no string index signature, right?

type Oops = Exclude<keyof Remapped, never>
declare let x: Oops;
x = sym;
x = "str";
// type Oops = typeof sym <-- what happened to "str"?

// equivalently, with an unresolved generic (no `exclude` shenanigans, since conditions won't execute):
function f<T>() {
type Orig = { [k: string]: any, str: any, [sym]: any } & T;

type Okay = keyof Orig;
let a: Okay;
a = "str";
a = sym;
a = "whatever";
// type Okay = string | number | typeof sym

type Remapped = { [K in keyof Orig as {} extends Record<K, any> ? never : K]: any }
/* type Remapped = {
str: any;
[sym]: any;
} */
// no string index signature, right?

type Oops = keyof Remapped;
let x: Oops;
x = sym;
x = "str";
}

// and another generic case with a _distributive_ mapping, to trigger a different branch in `getIndexType`
function g<T>() {
type Orig = { [k: string]: any, str: any, [sym]: any } & T;

type Okay = keyof Orig;
let a: Okay;
a = "str";
a = sym;
a = "whatever";
// type Okay = string | number | typeof sym

type NonIndex<T extends PropertyKey> = {} extends Record<T, any> ? never : T;
type DistributiveNonIndex<T extends PropertyKey> = T extends unknown ? NonIndex<T> : never;

type Remapped = { [K in keyof Orig as DistributiveNonIndex<K>]: any }
/* type Remapped = {
str: any;
[sym]: any;
} */
// no string index signature, right?

type Oops = keyof Remapped;
let x: Oops;
x = sym;
x = "str";
}

// https://github.com/microsoft/TypeScript/issues/57827

type StringKeys<T> = Extract<
keyof {
[P in keyof T as T[P] extends string ? P : never]: any;
},
string
>;

function test_57827<T>(z: StringKeys<T>) {
const f: string = z;
z = "foo"; // error
~
!!! error TS2322: Type 'string' is not assignable to type 'Extract<keyof { [P in keyof T as T[P] extends string ? P : never]: any; }, string>'.
}

type StringKeys2<T> = keyof {
[P in keyof T as T[P] extends string ? P : never]: any;
};

function h<T>(z: StringKeys2<T>) {
z = "foo"; // error
~
!!! error TS2322: Type 'string' is not assignable to type 'keyof { [P in keyof T as T[P] extends string ? P : never]: any; }'.
!!! error TS2322: Type 'string' is not assignable to type 'T[P] extends string ? P : never'.
const f: string = z; // ok
}

export {};

Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,21 @@ x = "str";
// equivalently, with an unresolved generic (no `exclude` shenanigans, since conditions won't execute):
function f<T>() {
type Orig = { [k: string]: any, str: any, [sym]: any } & T;

type Okay = keyof Orig;
let a: Okay;
a = "str";
a = sym;
a = "whatever";
// type Okay = string | number | typeof sym

type Remapped = { [K in keyof Orig as {} extends Record<K, any> ? never : K]: any }
/* type Remapped = {
str: any;
[sym]: any;
} */
// no string index signature, right?

type Oops = keyof Remapped;
let x: Oops;
x = sym;
Expand All @@ -47,7 +47,7 @@ function f<T>() {
// and another generic case with a _distributive_ mapping, to trigger a different branch in `getIndexType`
function g<T>() {
type Orig = { [k: string]: any, str: any, [sym]: any } & T;

type Okay = keyof Orig;
let a: Okay;
a = "str";
Expand All @@ -57,21 +57,45 @@ function g<T>() {

type NonIndex<T extends PropertyKey> = {} extends Record<T, any> ? never : T;
type DistributiveNonIndex<T extends PropertyKey> = T extends unknown ? NonIndex<T> : never;

type Remapped = { [K in keyof Orig as DistributiveNonIndex<K>]: any }
/* type Remapped = {
str: any;
[sym]: any;
} */
// no string index signature, right?

type Oops = keyof Remapped;
let x: Oops;
x = sym;
x = "str";
}

export {};
// https://github.com/microsoft/TypeScript/issues/57827

type StringKeys<T> = Extract<
keyof {
[P in keyof T as T[P] extends string ? P : never]: any;
},
string
>;

function test_57827<T>(z: StringKeys<T>) {
const f: string = z;
z = "foo"; // error
}

type StringKeys2<T> = keyof {
[P in keyof T as T[P] extends string ? P : never]: any;
};

function h<T>(z: StringKeys2<T>) {
z = "foo"; // error
const f: string = z; // ok
}

export {};


//// [keyRemappingKeyofResult.js]
const sym = Symbol("");
Expand All @@ -98,4 +122,12 @@ function g() {
x = sym;
x = "str";
}
function test_57827(z) {
const f = z;
z = "foo"; // error
}
function h(z) {
z = "foo"; // error
const f = z; // ok
}
export {};
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function f<T>() {
>[sym] : Symbol([sym], Decl(keyRemappingKeyofResult.ts, 21, 45))
>sym : Symbol(sym, Decl(keyRemappingKeyofResult.ts, 0, 5))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 20, 11))

type Okay = keyof Orig;
>Okay : Symbol(Okay, Decl(keyRemappingKeyofResult.ts, 21, 63))
>Orig : Symbol(Orig, Decl(keyRemappingKeyofResult.ts, 20, 17))
Expand All @@ -83,7 +83,7 @@ function f<T>() {
>a : Symbol(a, Decl(keyRemappingKeyofResult.ts, 24, 7))

// type Okay = string | number | typeof sym

type Remapped = { [K in keyof Orig as {} extends Record<K, any> ? never : K]: any }
>Remapped : Symbol(Remapped, Decl(keyRemappingKeyofResult.ts, 27, 19))
>K : Symbol(K, Decl(keyRemappingKeyofResult.ts, 30, 23))
Expand All @@ -97,7 +97,7 @@ function f<T>() {
[sym]: any;
} */
// no string index signature, right?

type Oops = keyof Remapped;
>Oops : Symbol(Oops, Decl(keyRemappingKeyofResult.ts, 30, 87))
>Remapped : Symbol(Remapped, Decl(keyRemappingKeyofResult.ts, 27, 19))
Expand Down Expand Up @@ -126,7 +126,7 @@ function g<T>() {
>[sym] : Symbol([sym], Decl(keyRemappingKeyofResult.ts, 45, 45))
>sym : Symbol(sym, Decl(keyRemappingKeyofResult.ts, 0, 5))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 44, 11))

type Okay = keyof Orig;
>Okay : Symbol(Okay, Decl(keyRemappingKeyofResult.ts, 45, 63))
>Orig : Symbol(Orig, Decl(keyRemappingKeyofResult.ts, 44, 17))
Expand Down Expand Up @@ -162,7 +162,7 @@ function g<T>() {
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 55, 30))
>NonIndex : Symbol(NonIndex, Decl(keyRemappingKeyofResult.ts, 51, 19))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 55, 30))

type Remapped = { [K in keyof Orig as DistributiveNonIndex<K>]: any }
>Remapped : Symbol(Remapped, Decl(keyRemappingKeyofResult.ts, 55, 95))
>K : Symbol(K, Decl(keyRemappingKeyofResult.ts, 57, 23))
Expand All @@ -175,7 +175,7 @@ function g<T>() {
[sym]: any;
} */
// no string index signature, right?

type Oops = keyof Remapped;
>Oops : Symbol(Oops, Decl(keyRemappingKeyofResult.ts, 57, 73))
>Remapped : Symbol(Remapped, Decl(keyRemappingKeyofResult.ts, 55, 95))
Expand All @@ -192,4 +192,67 @@ function g<T>() {
>x : Symbol(x, Decl(keyRemappingKeyofResult.ts, 65, 7))
}

// https://github.com/microsoft/TypeScript/issues/57827

type StringKeys<T> = Extract<
>StringKeys : Symbol(StringKeys, Decl(keyRemappingKeyofResult.ts, 68, 1))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 72, 16))
>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --))

keyof {
[P in keyof T as T[P] extends string ? P : never]: any;
>P : Symbol(P, Decl(keyRemappingKeyofResult.ts, 74, 5))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 72, 16))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 72, 16))
>P : Symbol(P, Decl(keyRemappingKeyofResult.ts, 74, 5))
>P : Symbol(P, Decl(keyRemappingKeyofResult.ts, 74, 5))

},
string
>;

function test_57827<T>(z: StringKeys<T>) {
>test_57827 : Symbol(test_57827, Decl(keyRemappingKeyofResult.ts, 77, 2))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 79, 20))
>z : Symbol(z, Decl(keyRemappingKeyofResult.ts, 79, 23))
>StringKeys : Symbol(StringKeys, Decl(keyRemappingKeyofResult.ts, 68, 1))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 79, 20))

const f: string = z;
>f : Symbol(f, Decl(keyRemappingKeyofResult.ts, 80, 7))
>z : Symbol(z, Decl(keyRemappingKeyofResult.ts, 79, 23))

z = "foo"; // error
>z : Symbol(z, Decl(keyRemappingKeyofResult.ts, 79, 23))
}

type StringKeys2<T> = keyof {
>StringKeys2 : Symbol(StringKeys2, Decl(keyRemappingKeyofResult.ts, 82, 1))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 84, 17))

[P in keyof T as T[P] extends string ? P : never]: any;
>P : Symbol(P, Decl(keyRemappingKeyofResult.ts, 85, 3))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 84, 17))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 84, 17))
>P : Symbol(P, Decl(keyRemappingKeyofResult.ts, 85, 3))
>P : Symbol(P, Decl(keyRemappingKeyofResult.ts, 85, 3))

};

function h<T>(z: StringKeys2<T>) {
>h : Symbol(h, Decl(keyRemappingKeyofResult.ts, 86, 2))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 88, 11))
>z : Symbol(z, Decl(keyRemappingKeyofResult.ts, 88, 14))
>StringKeys2 : Symbol(StringKeys2, Decl(keyRemappingKeyofResult.ts, 82, 1))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 88, 11))

z = "foo"; // error
>z : Symbol(z, Decl(keyRemappingKeyofResult.ts, 88, 14))

const f: string = z; // ok
>f : Symbol(f, Decl(keyRemappingKeyofResult.ts, 90, 7))
>z : Symbol(z, Decl(keyRemappingKeyofResult.ts, 88, 14))
}

export {};

Loading