Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
30 changes: 20 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15299,7 +15299,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const constraint = getResolvedBaseConstraint(type as InstantiableType | UnionOrIntersectionType);
return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined;
}
return type.flags & TypeFlags.Index ? stringNumberSymbolType : undefined;
if (type.flags & TypeFlags.Index) {
if (isGenericMappedType((type as IndexType).type)) {
const nameType = getNameTypeFromMappedType((type as IndexType).type as MappedType);
if (nameType) {
return getBaseConstraintOfType(nameType);
}
}
return stringNumberSymbolType;
}
return undefined;
}

/**
Expand Down Expand Up @@ -23529,7 +23538,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return result;
}

function getApparentMappedTypeKeys(nameType: Type, targetType: MappedType) {
function getApparentTargetMappedTypeKeys(nameType: Type, targetType: MappedType) {
const modifiersType = getApparentType(getModifiersTypeFromMappedType(targetType));
const mappedKeys: Type[] = [];
forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(
Expand Down Expand Up @@ -23691,6 +23700,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return result;
}
}
if (sourceFlags & TypeFlags.TypeParameter && source.symbol && some(source.symbol.declarations, d => d.parent.kind === SyntaxKind.MappedType)) {
const constraint = getConstraintOfTypeParameter(source);
if (constraint && isRelatedTo(constraint, target, RecursionFlags.Both, /*reportErrors*/ false) === Ternary.True) {
return Ternary.True;
}
}
if (isTupleType(targetType)) {
// An index type can have a tuple type target when the tuple type contains variadic elements.
// Check if the source is related to the known keys of the tuple type.
Expand Down Expand Up @@ -23722,7 +23737,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (nameType && isMappedTypeWithKeyofConstraintDeclaration(targetType)) {
// we need to get the apparent mappings and union them with the generic mappings, since some properties may be
// missing from the `constraintType` which will otherwise be mapped in the object
const mappedKeys = getApparentMappedTypeKeys(nameType, targetType);
const mappedKeys = getApparentTargetMappedTypeKeys(nameType, targetType);
// We still need to include the non-apparent (and thus still generic) keys in the target side of the comparison (in case they're in the source side)
targetKeys = getUnionType([mappedKeys, nameType]);
}
Expand Down Expand Up @@ -23924,12 +23939,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (isDeferredMappedIndex) {
const mappedType = (source as IndexType).type as MappedType;
const nameType = getNameTypeFromMappedType(mappedType);
// Unlike on the target side, on the source side we do *not* include the generic part of the `nameType`, since that comes from a
// (potentially anonymous) mapped type local type parameter, so that'd never assign outside the mapped type body, but we still want to
// 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));
const sourceMappedKeys = nameType || getConstraintTypeFromMappedType(mappedType);
if (result = isRelatedTo(sourceMappedKeys, target, RecursionFlags.Source, reportErrors)) {
return result;
}
Expand Down Expand Up @@ -24176,7 +24186,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMapper : reportUnreliableMapper);
if (result = isRelatedTo(targetConstraint, sourceConstraint, RecursionFlags.Both, reportErrors)) {
const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]);
if (instantiateType(getNameTypeFromMappedType(source), mapper) === instantiateType(getNameTypeFromMappedType(target), mapper)) {
if (isTypeIdenticalTo(instantiateType(getNameTypeFromMappedType(source), mapper) ?? unknownType, instantiateType(getNameTypeFromMappedType(target), mapper) ?? unknownType)) {
return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), RecursionFlags.Both, reportErrors);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
keyRemappingKeyofResult.ts(69,5): error TS2322: Type 'string' is not assignable to type 'keyof Remapped'.
Type '"whatever"' is not assignable to type 'unique symbol | "str" | DistributiveNonIndex<K>'.
keyRemappingKeyofResult.ts(83,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(91,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 (1 errors) ====
==== keyRemappingKeyofResult.ts (3 errors) ====
const sym = Symbol("")
type Orig = { [k: string]: any, str: any, [sym]: any }

Expand All @@ -25,21 +28,21 @@ keyRemappingKeyofResult.ts(69,5): error TS2322: Type 'string' is not assignable
// 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 @@ -49,7 +52,7 @@ keyRemappingKeyofResult.ts(69,5): error TS2322: Type 'string' is not assignable
// 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 @@ -59,14 +62,14 @@ keyRemappingKeyofResult.ts(69,5): error TS2322: Type 'string' is not assignable

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;
Expand All @@ -77,4 +80,33 @@ keyRemappingKeyofResult.ts(69,5): error TS2322: Type 'string' is not assignable
!!! error TS2322: Type '"whatever"' is not assignable to type 'unique symbol | "str" | DistributiveNonIndex<K>'.
}

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
~
!!! 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,22 +57,46 @@ 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";
x = "whatever"; // error
}

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 Down Expand Up @@ -100,4 +124,12 @@ function g() {
x = "str";
x = "whatever"; // error
}
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 @@ -195,4 +195,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, 69, 1))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 73, 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, 75, 5))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 73, 16))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 73, 16))
>P : Symbol(P, Decl(keyRemappingKeyofResult.ts, 75, 5))
>P : Symbol(P, Decl(keyRemappingKeyofResult.ts, 75, 5))

},
string
>;

function test_57827<T>(z: StringKeys<T>) {
>test_57827 : Symbol(test_57827, Decl(keyRemappingKeyofResult.ts, 78, 2))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 80, 20))
>z : Symbol(z, Decl(keyRemappingKeyofResult.ts, 80, 23))
>StringKeys : Symbol(StringKeys, Decl(keyRemappingKeyofResult.ts, 69, 1))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 80, 20))

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

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

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

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

};

function h<T>(z: StringKeys2<T>) {
>h : Symbol(h, Decl(keyRemappingKeyofResult.ts, 87, 2))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 89, 11))
>z : Symbol(z, Decl(keyRemappingKeyofResult.ts, 89, 14))
>StringKeys2 : Symbol(StringKeys2, Decl(keyRemappingKeyofResult.ts, 83, 1))
>T : Symbol(T, Decl(keyRemappingKeyofResult.ts, 89, 11))

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

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

export {};

Loading
Loading