Skip to content

Commit 0c876d2

Browse files
committed
Add workaroud for distributive conditional <-> negated key assignability
1 parent a00b970 commit 0c876d2

5 files changed

Lines changed: 356 additions & 1 deletion

File tree

‎tsc/internal/checker/relater.go‎

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3616,7 +3616,8 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo
36163616
nameType := r.c.getNameTypeFromMappedType(targetType)
36173617
constraintType := r.c.getConstraintTypeFromMappedType(targetType)
36183618
var targetKeys *Type
3619-
if nameType != nil && r.c.isMappedTypeWithKeyofConstraintDeclaration(targetType) {
3619+
isFiltering := nameType != nil && r.c.isMappedTypeWithKeyofConstraintDeclaration(targetType)
3620+
if isFiltering {
36203621
// we need to get the apparent mappings and union them with the generic mappings, since some properties may be
36213622
// missing from the `constraintType` which will otherwise be mapped in the object
36223623
mappedKeys := r.c.getApparentMappedTypeKeys(nameType, targetType)
@@ -3627,6 +3628,17 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo
36273628
} else {
36283629
targetKeys = constraintType
36293630
}
3631+
if isFiltering && source.flags&TypeFlagsIntersection != 0 && r.someTypeRelatedToType(source, constraintType, false, intersectionState) == TernaryTrue {
3632+
// run the filter over `source` - some part of `source` is related to the constraint - if source's other elements end up filtering the same way the nameType does, the assignment can pass
3633+
// This effectively is a workaround for a lack of distributive conditional/distributive homomorphic mapped types relationship rules that uses execution of the distribution (even at higher order)
3634+
// to approximate a better result.
3635+
// See the homomorphic case in tsc\testdata\tests\cases\compiler\indexMappedTypeWithFilteredKeysWithNegation.ts
3636+
sourceInstantiatedTarget := r.c.instantiateType(nameType, newSimpleTypeMapper(r.c.getTypeParameterFromMappedType(targetType), source))
3637+
broadTargetKeys := r.c.getUnionType([]*Type{targetKeys, sourceInstantiatedTarget})
3638+
if r.isRelatedTo(source, broadTargetKeys, RecursionFlagsTarget, false) == TernaryTrue {
3639+
return TernaryTrue
3640+
}
3641+
}
36303642
if r.isRelatedTo(source, targetKeys, RecursionFlagsTarget, reportErrors) == TernaryTrue {
36313643
return TernaryTrue
36323644
}
@@ -3663,6 +3675,11 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo
36633675
return result
36643676
}
36653677
}
3678+
} else if simplifiedTarget := r.getOptionalSimplificationOfConditional(target); simplifiedTarget != nil {
3679+
result = r.isRelatedToEx(source, simplifiedTarget, RecursionFlagsTarget, false /*reportErrors*/, nil /*headMessage*/, intersectionState)
3680+
if result != TernaryFalse {
3681+
return result
3682+
}
36663683
}
36673684
case target.flags&TypeFlagsTemplateLiteral != 0:
36683685
if source.flags&TypeFlagsTemplateLiteral != 0 {
@@ -4007,6 +4024,34 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo
40074024
return TernaryFalse
40084025
}
40094026

4027+
func (r *Relater) getOptionalSimplificationOfConditional(target *Type) *Type {
4028+
if target.flags&TypeFlagsConditional == 0 {
4029+
return nil
4030+
}
4031+
// TODO: This is a kludge for not having proper rules for relating distributive conditional types.
4032+
// We rewrite `A extends B ? never : Q<A>` into `Q<A & not B>` - provided that substitution
4033+
// actually changes it. This is not *particularly* accurate, but enables the common case of relating a filtering mapped type to the equivalent filtering negated type.
4034+
// See the non-homomorphic case in tsc\testdata\tests\cases\compiler\indexMappedTypeWithFilteredKeysWithNegation.ts for an example.
4035+
root := target.AsConditionalType().root
4036+
if root.isDistributive && r.c.getTrueTypeFromConditionalType(target).flags&TypeFlagsNever != 0 {
4037+
f := r.c.getFalseTypeFromConditionalType(target)
4038+
remappedFalseType := r.c.instantiateType(
4039+
r.c.getTypeFromTypeNode(root.node.FalseType),
4040+
r.c.combineTypeMappers(
4041+
newSimpleTypeMapper(
4042+
getNonDistributedTypeParameter(root.checkType),
4043+
r.c.getIntersectionType([]*Type{root.checkType, r.c.getNegatedType(root.extendsType)}),
4044+
),
4045+
target.AsConditionalType().mapper,
4046+
),
4047+
)
4048+
if remappedFalseType != f {
4049+
return remappedFalseType
4050+
}
4051+
}
4052+
return nil
4053+
}
4054+
40104055
func (r *Relater) typeArgumentsRelatedTo(sources []*Type, targets []*Type, variances []VarianceFlags, reportErrors bool, intersectionState IntersectionState) Ternary {
40114056
if len(sources) != len(targets) && r.relation == r.c.identityRelation {
40124057
return TernaryFalse
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//// [tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegation.ts] ////
2+
3+
//// [indexMappedTypeWithFilteredKeysWithNegation.ts]
4+
// homomorphic - mapped type causes `keyof ExcludeKey` to not simplify, relater operates with full mapped type awareness
5+
type ExcludeKey<T, TKey> = {
6+
[K in keyof T as K extends TKey ? never : K]: T[K]
7+
};
8+
9+
interface Test {
10+
a: string;
11+
b: number;
12+
c: boolean;
13+
}
14+
15+
declare const test: ExcludeKey<Test, "b">;
16+
declare var possibleKey: string & keyof Test & not "b";
17+
18+
test[possibleKey];
19+
20+
function f<T extends Test>(test: ExcludeKey<T, "b">, possibleKey: string & keyof T & not "b") {
21+
test[possibleKey];
22+
}
23+
24+
// non-homomorphic - mapped type declared with `keyof T & string` causes `keyof PathTuples` to simplify to `(keyof T & string) extends '_key' ? never : (keyof T & string)`
25+
// relater must operate without mapped type awareness
26+
// ultimate comparison is `keyof T & string & not '_key'` to `(keyof T & string) extends '_key' ? never : (keyof T & string)`
27+
type LocalExclude<T, U> = T & not U;
28+
type ChildPaths<T> = [PathTuples<T>] extends [never] ? [] : PathTuples<T>;
29+
type PathTuples<T> = T extends object
30+
? {
31+
[K in keyof T & string as K extends '_key' ? never : K]: [PathTuples<T[K]>] extends [never]
32+
? never
33+
: readonly [K, ...ChildPaths<T[K]>];
34+
}[LocalExclude<keyof T & string, '_key'>]
35+
: [];
36+
37+
type T1 = PathTuples<{ a: string; _key: string; b: { c: number } }>;
38+
const t1: T1 = ['b', 'c'];
39+
40+
type S1<T> = { [K in keyof T & string as K extends '_key' ? never : K]: 1 }[LocalExclude<keyof T & string, '_key'>];
41+
type S2<T> = { [K in string & keyof T as K extends '_key' ? never : K]: 1 }[LocalExclude<string & keyof T, '_key'>];
42+
43+
44+
//// [indexMappedTypeWithFilteredKeysWithNegation.js]
45+
"use strict";
46+
test[possibleKey];
47+
function f(test, possibleKey) {
48+
test[possibleKey];
49+
}
50+
const t1 = ['b', 'c'];
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
//// [tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegation.ts] ////
2+
3+
=== indexMappedTypeWithFilteredKeysWithNegation.ts ===
4+
// homomorphic - mapped type causes `keyof ExcludeKey` to not simplify, relater operates with full mapped type awareness
5+
type ExcludeKey<T, TKey> = {
6+
>ExcludeKey : Symbol(ExcludeKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 0, 0))
7+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 1, 16))
8+
>TKey : Symbol(TKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 1, 18))
9+
10+
[K in keyof T as K extends TKey ? never : K]: T[K]
11+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 2, 5))
12+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 1, 16))
13+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 2, 5))
14+
>TKey : Symbol(TKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 1, 18))
15+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 2, 5))
16+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 1, 16))
17+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 2, 5))
18+
19+
};
20+
21+
interface Test {
22+
>Test : Symbol(Test, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 3, 2))
23+
24+
a: string;
25+
>a : Symbol(Test.a, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 5, 16))
26+
27+
b: number;
28+
>b : Symbol(Test.b, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 6, 14))
29+
30+
c: boolean;
31+
>c : Symbol(Test.c, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 7, 14))
32+
}
33+
34+
declare const test: ExcludeKey<Test, "b">;
35+
>test : Symbol(test, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 11, 13))
36+
>ExcludeKey : Symbol(ExcludeKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 0, 0))
37+
>Test : Symbol(Test, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 3, 2))
38+
39+
declare var possibleKey: string & keyof Test & not "b";
40+
>possibleKey : Symbol(possibleKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 12, 11))
41+
>Test : Symbol(Test, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 3, 2))
42+
43+
test[possibleKey];
44+
>test : Symbol(test, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 11, 13))
45+
>possibleKey : Symbol(possibleKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 12, 11))
46+
47+
function f<T extends Test>(test: ExcludeKey<T, "b">, possibleKey: string & keyof T & not "b") {
48+
>f : Symbol(f, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 14, 18))
49+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 16, 11))
50+
>Test : Symbol(Test, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 3, 2))
51+
>test : Symbol(test, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 16, 27))
52+
>ExcludeKey : Symbol(ExcludeKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 0, 0))
53+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 16, 11))
54+
>possibleKey : Symbol(possibleKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 16, 52))
55+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 16, 11))
56+
57+
test[possibleKey];
58+
>test : Symbol(test, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 16, 27))
59+
>possibleKey : Symbol(possibleKey, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 16, 52))
60+
}
61+
62+
// non-homomorphic - mapped type declared with `keyof T & string` causes `keyof PathTuples` to simplify to `(keyof T & string) extends '_key' ? never : (keyof T & string)`
63+
// relater must operate without mapped type awareness
64+
// ultimate comparison is `keyof T & string & not '_key'` to `(keyof T & string) extends '_key' ? never : (keyof T & string)`
65+
type LocalExclude<T, U> = T & not U;
66+
>LocalExclude : Symbol(LocalExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 18, 1))
67+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 23, 18))
68+
>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 23, 20))
69+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 23, 18))
70+
>U : Symbol(U, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 23, 20))
71+
72+
type ChildPaths<T> = [PathTuples<T>] extends [never] ? [] : PathTuples<T>;
73+
>ChildPaths : Symbol(ChildPaths, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 23, 36))
74+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 24, 16))
75+
>PathTuples : Symbol(PathTuples, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 24, 74))
76+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 24, 16))
77+
>PathTuples : Symbol(PathTuples, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 24, 74))
78+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 24, 16))
79+
80+
type PathTuples<T> = T extends object
81+
>PathTuples : Symbol(PathTuples, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 24, 74))
82+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 25, 16))
83+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 25, 16))
84+
85+
? {
86+
[K in keyof T & string as K extends '_key' ? never : K]: [PathTuples<T[K]>] extends [never]
87+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 27, 7))
88+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 25, 16))
89+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 27, 7))
90+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 27, 7))
91+
>PathTuples : Symbol(PathTuples, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 24, 74))
92+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 25, 16))
93+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 27, 7))
94+
95+
? never
96+
: readonly [K, ...ChildPaths<T[K]>];
97+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 27, 7))
98+
>ChildPaths : Symbol(ChildPaths, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 23, 36))
99+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 25, 16))
100+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 27, 7))
101+
102+
}[LocalExclude<keyof T & string, '_key'>]
103+
>LocalExclude : Symbol(LocalExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 18, 1))
104+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 25, 16))
105+
106+
: [];
107+
108+
type T1 = PathTuples<{ a: string; _key: string; b: { c: number } }>;
109+
>T1 : Symbol(T1, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 31, 7))
110+
>PathTuples : Symbol(PathTuples, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 24, 74))
111+
>a : Symbol(a, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 33, 22))
112+
>_key : Symbol(_key, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 33, 33))
113+
>b : Symbol(b, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 33, 47))
114+
>c : Symbol(c, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 33, 52))
115+
116+
const t1: T1 = ['b', 'c'];
117+
>t1 : Symbol(t1, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 34, 5))
118+
>T1 : Symbol(T1, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 31, 7))
119+
120+
type S1<T> = { [K in keyof T & string as K extends '_key' ? never : K]: 1 }[LocalExclude<keyof T & string, '_key'>];
121+
>S1 : Symbol(S1, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 34, 26))
122+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 36, 8))
123+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 36, 16))
124+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 36, 8))
125+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 36, 16))
126+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 36, 16))
127+
>LocalExclude : Symbol(LocalExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 18, 1))
128+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 36, 8))
129+
130+
type S2<T> = { [K in string & keyof T as K extends '_key' ? never : K]: 1 }[LocalExclude<string & keyof T, '_key'>];
131+
>S2 : Symbol(S2, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 36, 116))
132+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 37, 8))
133+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 37, 16))
134+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 37, 8))
135+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 37, 16))
136+
>K : Symbol(K, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 37, 16))
137+
>LocalExclude : Symbol(LocalExclude, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 18, 1))
138+
>T : Symbol(T, Decl(indexMappedTypeWithFilteredKeysWithNegation.ts, 37, 8))
139+
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//// [tests/cases/compiler/indexMappedTypeWithFilteredKeysWithNegation.ts] ////
2+
3+
=== indexMappedTypeWithFilteredKeysWithNegation.ts ===
4+
// homomorphic - mapped type causes `keyof ExcludeKey` to not simplify, relater operates with full mapped type awareness
5+
type ExcludeKey<T, TKey> = {
6+
>ExcludeKey : ExcludeKey<T, TKey>
7+
8+
[K in keyof T as K extends TKey ? never : K]: T[K]
9+
};
10+
11+
interface Test {
12+
a: string;
13+
>a : string
14+
15+
b: number;
16+
>b : number
17+
18+
c: boolean;
19+
>c : boolean
20+
}
21+
22+
declare const test: ExcludeKey<Test, "b">;
23+
>test : ExcludeKey<Test, "b">
24+
25+
declare var possibleKey: string & keyof Test & not "b";
26+
>possibleKey : "a" | "c"
27+
28+
test[possibleKey];
29+
>test[possibleKey] : string | boolean
30+
>test : ExcludeKey<Test, "b">
31+
>possibleKey : "a" | "c"
32+
33+
function f<T extends Test>(test: ExcludeKey<T, "b">, possibleKey: string & keyof T & not "b") {
34+
>f : <T extends Test>(test: ExcludeKey<T, "b">, possibleKey: string & keyof T & not "b") => void
35+
>test : ExcludeKey<T, "b">
36+
>possibleKey : string & keyof T & not "b"
37+
38+
test[possibleKey];
39+
>test[possibleKey] : ExcludeKey<T, "b">[string & keyof T & not "b"]
40+
>test : ExcludeKey<T, "b">
41+
>possibleKey : string & keyof T & not "b"
42+
}
43+
44+
// non-homomorphic - mapped type declared with `keyof T & string` causes `keyof PathTuples` to simplify to `(keyof T & string) extends '_key' ? never : (keyof T & string)`
45+
// relater must operate without mapped type awareness
46+
// ultimate comparison is `keyof T & string & not '_key'` to `(keyof T & string) extends '_key' ? never : (keyof T & string)`
47+
type LocalExclude<T, U> = T & not U;
48+
>LocalExclude : LocalExclude<T, U>
49+
50+
type ChildPaths<T> = [PathTuples<T>] extends [never] ? [] : PathTuples<T>;
51+
>ChildPaths : ChildPaths<T>
52+
53+
type PathTuples<T> = T extends object
54+
>PathTuples : PathTuples<T>
55+
56+
? {
57+
[K in keyof T & string as K extends '_key' ? never : K]: [PathTuples<T[K]>] extends [never]
58+
? never
59+
: readonly [K, ...ChildPaths<T[K]>];
60+
}[LocalExclude<keyof T & string, '_key'>]
61+
: [];
62+
63+
type T1 = PathTuples<{ a: string; _key: string; b: { c: number } }>;
64+
>T1 : readonly ["a"] | readonly ["b", "c"]
65+
>a : string
66+
>_key : string
67+
>b : { c: number; }
68+
>c : number
69+
70+
const t1: T1 = ['b', 'c'];
71+
>t1 : readonly ["a"] | readonly ["b", "c"]
72+
>['b', 'c'] : ["b", "c"]
73+
>'b' : "b"
74+
>'c' : "c"
75+
76+
type S1<T> = { [K in keyof T & string as K extends '_key' ? never : K]: 1 }[LocalExclude<keyof T & string, '_key'>];
77+
>S1 : S1<T>
78+
79+
type S2<T> = { [K in string & keyof T as K extends '_key' ? never : K]: 1 }[LocalExclude<string & keyof T, '_key'>];
80+
>S2 : S2<T>
81+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// @strict: true
2+
3+
// homomorphic - mapped type causes `keyof ExcludeKey` to not simplify, relater operates with full mapped type awareness
4+
type ExcludeKey<T, TKey> = {
5+
[K in keyof T as K extends TKey ? never : K]: T[K]
6+
};
7+
8+
interface Test {
9+
a: string;
10+
b: number;
11+
c: boolean;
12+
}
13+
14+
declare const test: ExcludeKey<Test, "b">;
15+
declare var possibleKey: string & keyof Test & not "b";
16+
17+
test[possibleKey];
18+
19+
function f<T extends Test>(test: ExcludeKey<T, "b">, possibleKey: string & keyof T & not "b") {
20+
test[possibleKey];
21+
}
22+
23+
// non-homomorphic - mapped type declared with `keyof T & string` causes `keyof PathTuples` to simplify to `(keyof T & string) extends '_key' ? never : (keyof T & string)`
24+
// relater must operate without mapped type awareness
25+
// ultimate comparison is `keyof T & string & not '_key'` to `(keyof T & string) extends '_key' ? never : (keyof T & string)`
26+
type LocalExclude<T, U> = T & not U;
27+
type ChildPaths<T> = [PathTuples<T>] extends [never] ? [] : PathTuples<T>;
28+
type PathTuples<T> = T extends object
29+
? {
30+
[K in keyof T & string as K extends '_key' ? never : K]: [PathTuples<T[K]>] extends [never]
31+
? never
32+
: readonly [K, ...ChildPaths<T[K]>];
33+
}[LocalExclude<keyof T & string, '_key'>]
34+
: [];
35+
36+
type T1 = PathTuples<{ a: string; _key: string; b: { c: number } }>;
37+
const t1: T1 = ['b', 'c'];
38+
39+
type S1<T> = { [K in keyof T & string as K extends '_key' ? never : K]: 1 }[LocalExclude<keyof T & string, '_key'>];
40+
type S2<T> = { [K in string & keyof T as K extends '_key' ? never : K]: 1 }[LocalExclude<string & keyof T, '_key'>];

0 commit comments

Comments
 (0)