Skip to content

Commit 37521a7

Browse files
committed
Remove what got carved out to a separate bug fixing PR
1 parent 59b6549 commit 37521a7

12 files changed

+843
-780
lines changed

src/compiler/checker.ts

+20-22
Original file line numberDiff line numberDiff line change
@@ -2008,7 +2008,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
20082008
var numberOrBigIntType = getUnionType([numberType, bigintType]);
20092009
var templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType;
20102010
var numericStringType = getTemplateLiteralType(["", ""], [numberType]); // The `${number}` type
2011-
var keyofConstraintObject = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, [stringType, numberType, esSymbolType].map(t => createIndexInfo(t, unknownType, /*isReadonly*/ false))); // { [k: string | number | symbol]: unknown; }
20122011

20132012
var restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t, () => "(restrictive mapper)");
20142013
var permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t, () => "(permissive mapper)");
@@ -13668,15 +13667,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1366813667
return instantiateType(instantiable, createTypeMapper([type.indexType, type.objectType], [getNumberLiteralType(0), createTupleType([replacement])]));
1366913668
}
1367013669

13671-
// If the original mapped type had an union/intersection constraint
13672-
// there is a chance that it includes an intersection that could limit what members are allowed
13673-
function getReverseMappedTypeMembersLimitingConstraint(type: ReverseMappedType) {
13670+
// If the original mapped type had an intersection constraint we extract its components,
13671+
// and we make an attempt to do so even if the intersection has been reduced to a union.
13672+
// This entire process allows us to possibly retrieve the filtering type literals.
13673+
// e.g. { [K in keyof U & ("a" | "b") ] } -> "a" | "b"
13674+
function getLimitedConstraint(type: ReverseMappedType) {
1367413675
const constraint = getConstraintTypeFromMappedType(type.mappedType);
13675-
if (constraint === type.constraintType) {
13676+
if (!(constraint.flags & TypeFlags.Union || constraint.flags & TypeFlags.Intersection)) {
1367613677
return;
1367713678
}
13678-
const mapper = appendTypeMapping(type.mappedType.mapper, type.constraintType.type, keyofConstraintObject);
13679-
return getBaseConstraintOrType(instantiateType(constraint, mapper));
13679+
const origin = (constraint.flags & TypeFlags.Union) ? (constraint as UnionType).origin : (constraint as IntersectionType);
13680+
if (!origin || !(origin.flags & TypeFlags.Intersection)) {
13681+
return;
13682+
}
13683+
const limitedConstraint = getIntersectionType((origin as IntersectionType).types.filter(t => t !== type.constraintType));
13684+
return limitedConstraint !== neverType ? limitedConstraint : undefined;
1368013685
}
1368113686

1368213687
function resolveReverseMappedTypeMembers(type: ReverseMappedType) {
@@ -13686,23 +13691,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1368613691
const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional;
1368713692
const indexInfos = indexInfo ? [createIndexInfo(stringType, inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly)] : emptyArray;
1368813693
const members = createSymbolTable();
13689-
const membersLimitingConstraint = getReverseMappedTypeMembersLimitingConstraint(type);
13694+
const limitedConstraint = getLimitedConstraint(type);
1369013695
const nameType = getNameTypeFromMappedType(type.mappedType);
1369113696

1369213697
for (const prop of getPropertiesOfType(type.source)) {
13693-
// we skip those properties that are not assignable to either of those "limiters"
13694-
// the extra properties wouldn't get through the application of the mapped type anyway
13695-
// and their inferred type might not satisfy the type parameter's constraint
13696-
// which, in turn, could fail the check if the inferred type is assignable to its constraint
13697-
//
13698-
// inferring `{ a: number; b: string }` wouldn't satisfy T's constraint so b has to be skipped over here
13699-
//
13700-
// function fn<T extends Record<string, number>>({ [K in keyof T & "a"]: T[K] }): T
13701-
// const obj = { a: 1, b: '2' };
13702-
// fn(obj);
13703-
if (membersLimitingConstraint || nameType) {
13698+
// In case of a reverse mapped type with an intersection constraint or a name type
13699+
// we skip those properties that are not assignable to them
13700+
// because the extra properties wouldn't get through the application of the mapped type anyway
13701+
if (limitedConstraint || nameType) {
1370413702
const propertyNameType = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique);
13705-
if (membersLimitingConstraint && !isTypeAssignableTo(propertyNameType, membersLimitingConstraint)) {
13703+
if (limitedConstraint && !isTypeAssignableTo(propertyNameType, limitedConstraint)) {
1370613704
continue;
1370713705
}
1370813706
if (nameType) {
@@ -25760,9 +25758,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2576025758
}
2576125759

2576225760
function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean {
25763-
if (constraintType.flags & TypeFlags.UnionOrIntersection) {
25761+
if ((constraintType.flags & TypeFlags.Union) || (constraintType.flags & TypeFlags.Intersection)) {
2576425762
let result = false;
25765-
for (const type of (constraintType as UnionOrIntersectionType).types) {
25763+
for (const type of (constraintType as (UnionType | IntersectionType)).types) {
2576625764
result = inferToMappedType(source, target, type) || result;
2576725765
}
2576825766
return result;
+45-49
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
1-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(15,7): error TS2322: Type '"bar"' is not assignable to type '"foo"'.
2-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(28,3): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ entry: "foo"; states: { a: { entry: "foo"; }; }; }'.
3-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(39,3): error TS2353: Object literal may only specify known properties, and 'z' does not exist in type '{ x: number; y: "y"; }'.
4-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(53,7): error TS2322: Type '{ [K in keyof T & keyof Stuff]: T[K]; }' is not assignable to type 'T'.
1+
reverseMappedTypeIntersectionConstraint.ts(19,7): error TS2322: Type '"bar"' is not assignable to type '"foo"'.
2+
reverseMappedTypeIntersectionConstraint.ts(32,3): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ entry: "foo"; states: { a: { entry: "foo"; }; }; }'.
3+
reverseMappedTypeIntersectionConstraint.ts(43,3): error TS2353: Object literal may only specify known properties, and 'z' does not exist in type '{ x: number; y: "y"; }'.
4+
reverseMappedTypeIntersectionConstraint.ts(59,7): error TS2322: Type '{ [K in keyof T & keyof Stuff]: T[K]; }' is not assignable to type 'T'.
55
'{ [K in keyof T & keyof Stuff]: T[K]; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Stuff'.
6-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(57,64): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ field: 1; anotherField: "a"; }'.
7-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(63,7): error TS2322: Type '{ [K in keyof T & keyof Stuff]: T[K]; }[]' is not assignable to type 'T[]'.
6+
reverseMappedTypeIntersectionConstraint.ts(63,49): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ field: 1; anotherField: "a"; }'.
7+
reverseMappedTypeIntersectionConstraint.ts(69,7): error TS2322: Type '{ [K in keyof T & keyof Stuff]: T[K]; }[]' is not assignable to type 'T[]'.
88
Type '{ [K in keyof T & keyof Stuff]: T[K]; }' is not assignable to type 'T'.
99
'{ [K in keyof T & keyof Stuff]: T[K]; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Stuff'.
10-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(68,36): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ field: 1; anotherField: "a"; }'.
11-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(81,25): error TS2353: Object literal may only specify known properties, and 'y' does not exist in type '{ x: 1; }'.
12-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(92,25): error TS2353: Object literal may only specify known properties, and 'z' does not exist in type '{ x: 1; }'.
13-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(94,35): error TS2353: Object literal may only specify known properties, and 'z' does not exist in type '{ x: 1; y: "foo"; }'.
14-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(107,67): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ prop: "foo"; nested: { prop: string; }; }'.
15-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(152,3): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ types: { actors: { src: "str"; logic: () => Promise<string>; }; }; invoke: { readonly src: "str"; }; }'.
16-
reverseMappedTypeLimitedConstraintWithIntersection1.ts(159,3): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ invoke: { readonly src: "whatever"; }; }'.
10+
reverseMappedTypeIntersectionConstraint.ts(74,36): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ field: 1; anotherField: "a"; }'.
11+
reverseMappedTypeIntersectionConstraint.ts(87,12): error TS2353: Object literal may only specify known properties, and 'y' does not exist in type '{ x: 1; }'.
12+
reverseMappedTypeIntersectionConstraint.ts(98,12): error TS2353: Object literal may only specify known properties, and 'z' does not exist in type '{ x: 1; }'.
13+
reverseMappedTypeIntersectionConstraint.ts(100,22): error TS2353: Object literal may only specify known properties, and 'z' does not exist in type '{ x: 1; y: "foo"; }'.
14+
reverseMappedTypeIntersectionConstraint.ts(113,67): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ prop: "foo"; nested: { prop: string; }; }'.
15+
reverseMappedTypeIntersectionConstraint.ts(152,21): error TS2585: 'Promise' only refers to a type, but is being used as a value here. Do you need to change your target library? Try changing the 'lib' compiler option to es2015 or later.
16+
reverseMappedTypeIntersectionConstraint.ts(164,3): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ types: { actors: { src: "str"; logic: () => any; }; }; invoke: { readonly src: "str"; }; }'.
17+
reverseMappedTypeIntersectionConstraint.ts(171,3): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ invoke: { readonly src: "whatever"; }; }'.
1718

1819

19-
==== reverseMappedTypeLimitedConstraintWithIntersection1.ts (13 errors) ====
20+
==== reverseMappedTypeIntersectionConstraint.ts (14 errors) ====
2021
type StateConfig<TAction extends string> = {
2122
entry?: TAction
2223
states?: Record<string, StateConfig<TAction>>;
2324
};
2425

26+
type StateSchema = {
27+
states?: Record<string, StateSchema>;
28+
};
29+
2530
declare function createMachine<
2631
TConfig extends StateConfig<TAction>,
2732
TAction extends string = TConfig["entry"] extends string ? TConfig["entry"] : string,
@@ -34,7 +39,7 @@ reverseMappedTypeLimitedConstraintWithIntersection1.ts(159,3): error TS2353: Obj
3439
entry: "bar",
3540
~~~~~
3641
!!! error TS2322: Type '"bar"' is not assignable to type '"foo"'.
37-
!!! related TS6500 reverseMappedTypeLimitedConstraintWithIntersection1.ts:2:3: The expected type comes from property 'entry' which is declared here on type 'StateConfig<"foo">'
42+
!!! related TS6500 reverseMappedTypeIntersectionConstraint.ts:2:3: The expected type comes from property 'entry' which is declared here on type 'StateConfig<"foo">'
3843
},
3944
},
4045
extra: 12,
@@ -65,6 +70,8 @@ reverseMappedTypeLimitedConstraintWithIntersection1.ts(159,3): error TS2353: Obj
6570
!!! error TS2353: Object literal may only specify known properties, and 'z' does not exist in type '{ x: number; y: "y"; }'.
6671
});
6772

73+
checked;
74+
6875
// -----------------------------------------------------------------------------------------
6976

7077
interface Stuff {
@@ -83,8 +90,8 @@ reverseMappedTypeLimitedConstraintWithIntersection1.ts(159,3): error TS2353: Obj
8390
}
8491
}
8592

86-
const stuff1 = doStuffWithStuff({ field: 1, anotherField: 'a', extra: 123 })
87-
~~~~~
93+
doStuffWithStuff({ field: 1, anotherField: 'a', extra: 123 })
94+
~~~~~
8895
!!! error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ field: 1; anotherField: "a"; }'.
8996

9097
function doStuffWithStuffArr<T extends Stuff>(arr: { [K in keyof T & keyof Stuff]: T[K] }[]): T[] {
@@ -99,7 +106,7 @@ reverseMappedTypeLimitedConstraintWithIntersection1.ts(159,3): error TS2353: Obj
99106
}
100107
}
101108

102-
const stuff2 = doStuffWithStuffArr([
109+
doStuffWithStuffArr([
103110
{ field: 1, anotherField: 'a', extra: 123 },
104111
~~~~~
105112
!!! error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ field: 1; anotherField: "a"; }'.
@@ -109,31 +116,31 @@ reverseMappedTypeLimitedConstraintWithIntersection1.ts(159,3): error TS2353: Obj
109116

110117
type XNumber = { x: number }
111118

112-
declare function foo<T extends XNumber>(props: {[K in keyof T & keyof XNumber]: T[K]}): T;
119+
declare function foo<T extends XNumber>(props: {[K in keyof T & keyof XNumber]: T[K]}): void;
113120

114121
function bar(props: {x: number, y: string}) {
115122
return foo(props); // no error because lack of excess property check by design
116123
}
117124

118-
const foo1 = foo({x: 1, y: 'foo'});
119-
~
125+
foo({x: 1, y: 'foo'});
126+
~
120127
!!! error TS2353: Object literal may only specify known properties, and 'y' does not exist in type '{ x: 1; }'.
121128

122-
const foo2 = foo({...{x: 1, y: 'foo'}}); // no error because lack of excess property check by design
129+
foo({...{x: 1, y: 'foo'}}); // no error because lack of excess property check by design
123130

124131
// -----------------------------------------------------------------------------------------
125132

126133
type NoErrWithOptProps = { x: number, y?: string }
127134

128-
declare function baz<T extends NoErrWithOptProps>(props: {[K in keyof T & keyof NoErrWithOptProps]: T[K]}): T;
135+
declare function baz<T extends NoErrWithOptProps>(props: {[K in keyof T & keyof NoErrWithOptProps]: T[K]}): void;
129136

130-
const baz1 = baz({x: 1});
131-
const baz2 = baz({x: 1, z: 123});
132-
~
137+
baz({x: 1});
138+
baz({x: 1, z: 123});
139+
~
133140
!!! error TS2353: Object literal may only specify known properties, and 'z' does not exist in type '{ x: 1; }'.
134-
const baz3 = baz({x: 1, y: 'foo'});
135-
const baz4 = baz({x: 1, y: 'foo', z: 123});
136-
~
141+
baz({x: 1, y: 'foo'});
142+
baz({x: 1, y: 'foo', z: 123});
143+
~
137144
!!! error TS2353: Object literal may only specify known properties, and 'z' does not exist in type '{ x: 1; y: "foo"; }'.
138145

139146
// -----------------------------------------------------------------------------------------
@@ -155,6 +162,8 @@ reverseMappedTypeLimitedConstraintWithIntersection1.ts(159,3): error TS2353: Obj
155162

156163
type IsLiteralString<T extends string> = string extends T ? false : true;
157164

165+
type DeepWritable<T> = T extends Function ? T : { -readonly [K in keyof T]: DeepWritable<T[K]> }
166+
158167
interface ProvidedActor {
159168
src: string;
160169
logic: () => Promise<unknown>;
@@ -177,12 +186,18 @@ reverseMappedTypeLimitedConstraintWithIntersection1.ts(159,3): error TS2353: Obj
177186
};
178187
}
179188

189+
type NoExtra<T> = {
190+
[K in keyof T]: K extends keyof MachineConfig<any> ? T[K] : never
191+
}
192+
180193
declare function createXMachine<
181194
const TConfig extends MachineConfig<TActor>,
182195
TActor extends ProvidedActor = TConfig extends { types: { actors: ProvidedActor} } ? TConfig["types"]["actors"] : ProvidedActor,
183196
>(config: {[K in keyof MachineConfig<any> & keyof TConfig]: TConfig[K] }): TConfig;
184197

185198
const child = () => Promise.resolve("foo");
199+
~~~~~~~
200+
!!! error TS2585: 'Promise' only refers to a type, but is being used as a value here. Do you need to change your target library? Try changing the 'lib' compiler option to es2015 or later.
186201

187202
const config = createXMachine({
188203
types: {} as {
@@ -196,7 +211,7 @@ reverseMappedTypeLimitedConstraintWithIntersection1.ts(159,3): error TS2353: Obj
196211
},
197212
extra: 10
198213
~~~~~
199-
!!! error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ types: { actors: { src: "str"; logic: () => Promise<string>; }; }; invoke: { readonly src: "str"; }; }'.
214+
!!! error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ types: { actors: { src: "str"; logic: () => any; }; }; invoke: { readonly src: "str"; }; }'.
200215
});
201216

202217
const config2 = createXMachine({
@@ -207,23 +222,4 @@ reverseMappedTypeLimitedConstraintWithIntersection1.ts(159,3): error TS2353: Obj
207222
~~~~~
208223
!!! error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ invoke: { readonly src: "whatever"; }; }'.
209224
});
210-
211-
declare function fn1<T extends Record<string, number>>(obj: {
212-
[K in keyof T & "a"]: T[K];
213-
}): T;
214-
const obj1 = {
215-
a: 42,
216-
b: true,
217-
};
218-
const result1 = fn1(obj1);
219-
220-
declare function fn2<T extends Record<string, number>>(obj: {
221-
[K in (keyof T & "a") | "b"]: T[K];
222-
}): T;
223-
const obj2 = {
224-
a: 42,
225-
b: 100,
226-
c: true,
227-
};
228-
const result2 = fn2(obj2);
229225

0 commit comments

Comments
 (0)