Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

better support for nameType skipping properties logic #1

Open
wants to merge 8 commits into
base: infer-from-filterin-mapped-types
Choose a base branch
from
Open
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
52 changes: 35 additions & 17 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13693,24 +13693,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const members = createSymbolTable();
const limitedConstraint = getLimitedConstraint(type);
const nameType = getNameTypeFromMappedType(type.mappedType);
const sourceProperties = getPropertiesOfType(type.source);

for (const prop of getPropertiesOfType(type.source)) {
// In case of a reverse mapped type with an intersection constraint or a name type
// we skip those properties that are not assignable to them
// because the extra properties wouldn't get through the application of the mapped type anyway
if (limitedConstraint || nameType) {
const propertyNameType = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique);
if (limitedConstraint && !isTypeAssignableTo(propertyNameType, limitedConstraint)) {
continue;
}
if (nameType) {
const nameMapper = appendTypeMapping(type.mappedType.mapper, getTypeParameterFromMappedType(type.mappedType), propertyNameType);
const instantiatedNameType = instantiateType(nameType, nameMapper);
if (instantiatedNameType.flags & TypeFlags.Never) {
continue;
}
}
}
for (const prop of sourceProperties) {
const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0);
const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol;
inferredProp.declarations = prop.declarations;
Expand All @@ -13735,6 +13720,39 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
members.set(prop.escapedName, inferredProp);
}

setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos);

const propsToBeStrippedAway: Set<__String> = new Set();

for (const prop of sourceProperties) {
// In case of a reverse mapped type with an intersection constraint or a name type
// we skip those properties that are not assignable to them
// because the extra properties wouldn't get through the application of the mapped type anyway
//
// We do this after having set all the properties because we may need the full reverse-inferred type
// while stripping away some of its properties, it's sort of a circular dependency
// e.g. we need T[K] in { [K in keyof T as T[K] extends string ? K : never ]: { value: T[K] } }
if (limitedConstraint || nameType) {
const propertyNameType = getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique);
if (limitedConstraint && !isTypeAssignableTo(propertyNameType, limitedConstraint)) {
propsToBeStrippedAway.add(prop.escapedName);
}
if (nameType) {
const nameMapper = appendTypeMapping(type.mappedType.mapper, getTypeParameterFromMappedType(type.mappedType), propertyNameType);
const typeParameterMapper = appendTypeMapping(nameMapper, type.constraintType.type, type);
const instantiatedNameType = instantiateType(nameType, typeParameterMapper);
if (instantiatedNameType.flags & TypeFlags.Never) {
propsToBeStrippedAway.add(prop.escapedName);
}
}
}
}

for (const propToStripAway of propsToBeStrippedAway) {
members.delete(propToStripAway);
}

setStructuredTypeMembers(type, members, emptyArray, emptyArray, indexInfos);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ reverseMappedTypeInferFromFilteringNameType2.ts(85,36): error TS2353: Object lit
reverseMappedTypeInferFromFilteringNameType2.ts(96,68): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ prop: "foo"; nested: { prop: string; }; }'.
reverseMappedTypeInferFromFilteringNameType2.ts(139,3): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ readonly types: { actors: { src: "str"; logic: () => Promise<string>; }; }; readonly invoke: { readonly src: "str"; }; }'.
reverseMappedTypeInferFromFilteringNameType2.ts(145,3): error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ readonly invoke: { readonly src: "whatever"; }; }'.
reverseMappedTypeInferFromFilteringNameType2.ts(164,11): error TS2353: Object literal may only specify known properties, and 'a' does not exist in type 'Bombolo<{ enabled: false; }>'.
reverseMappedTypeInferFromFilteringNameType2.ts(178,3): error TS2353: Object literal may only specify known properties, and 'not_ok' does not exist in type 'TestRecursiveReferenceToT<{ ok: "it's a string"; }>'.


==== reverseMappedTypeInferFromFilteringNameType2.ts (13 errors) ====
==== reverseMappedTypeInferFromFilteringNameType2.ts (15 errors) ====
type StateConfig<TAction extends string> = {
entry?: TAction;
states?: Record<string, StateConfig<TAction>>;
Expand Down Expand Up @@ -193,4 +195,43 @@ reverseMappedTypeInferFromFilteringNameType2.ts(145,3): error TS2353: Object lit
~~~~~
!!! error TS2353: Object literal may only specify known properties, and 'extra' does not exist in type '{ readonly invoke: { readonly src: "whatever"; }; }'.
});



type Bombolo<T> = {
[
K in keyof T as K extends 'enabled'
? K
: 'enabled' extends keyof T
? T["enabled"] extends true
? K
: never
: never
]: T[K]
}


declare function bombolo<T>(a: Bombolo<T>): T

bombolo({ a: "a", b: "b", c: "c", enabled: false as const})
~
!!! error TS2353: Object literal may only specify known properties, and 'a' does not exist in type 'Bombolo<{ enabled: false; }>'.
bombolo({ a: "a", b: "b", c: "c", enabled: true as const})

// no excess property check because the parameter type turns out to be the empty object type {}
bombolo({ a: "a", b: "b", c: "c"})

type TestRecursiveReferenceToT<T> = {
[K in keyof T as T[K] extends string ? K : never ]: { a: T[K] }
}

declare function tstrcv<T>(a: TestRecursiveReferenceToT<T>): T

const res = tstrcv({
ok: { a: "it's a string" } as const,
not_ok: { a: 123 },
~~~~~~
!!! error TS2353: Object literal may only specify known properties, and 'not_ok' does not exist in type 'TestRecursiveReferenceToT<{ ok: "it's a string"; }>'.
})

res
// ^?
Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,107 @@ const config2 = createXMachine({

});


type Bombolo<T> = {
>Bombolo : Symbol(Bombolo, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 145, 3))
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 148, 13))

[
K in keyof T as K extends 'enabled'
>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 149, 5))
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 148, 13))
>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 149, 5))

? K
>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 149, 5))

: 'enabled' extends keyof T
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 148, 13))

? T["enabled"] extends true
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 148, 13))

? K
>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 149, 5))

: never
: never
]: T[K]
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 148, 13))
>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 149, 5))
}


declare function bombolo<T>(a: Bombolo<T>): T
>bombolo : Symbol(bombolo, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 158, 1))
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 161, 25))
>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 161, 28))
>Bombolo : Symbol(Bombolo, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 145, 3))
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 161, 25))
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 161, 25))

bombolo({ a: "a", b: "b", c: "c", enabled: false as const})
>bombolo : Symbol(bombolo, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 158, 1))
>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 163, 9))
>b : Symbol(b, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 163, 17))
>c : Symbol(c, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 163, 25))
>enabled : Symbol(enabled, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 163, 33))
>const : Symbol(const)

bombolo({ a: "a", b: "b", c: "c", enabled: true as const})
>bombolo : Symbol(bombolo, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 158, 1))
>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 164, 9))
>b : Symbol(b, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 164, 17))
>c : Symbol(c, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 164, 25))
>enabled : Symbol(enabled, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 164, 33))
>const : Symbol(const)

// no excess property check because the parameter type turns out to be the empty object type {}
bombolo({ a: "a", b: "b", c: "c"})
>bombolo : Symbol(bombolo, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 158, 1))
>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 167, 9))
>b : Symbol(b, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 167, 17))
>c : Symbol(c, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 167, 25))

type TestRecursiveReferenceToT<T> = {
>TestRecursiveReferenceToT : Symbol(TestRecursiveReferenceToT, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 167, 34))
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 169, 31))

[K in keyof T as T[K] extends string ? K : never ]: { a: T[K] }
>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 170, 5))
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 169, 31))
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 169, 31))
>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 170, 5))
>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 170, 5))
>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 170, 57))
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 169, 31))
>K : Symbol(K, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 170, 5))
}

declare function tstrcv<T>(a: TestRecursiveReferenceToT<T>): T
>tstrcv : Symbol(tstrcv, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 171, 1))
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 173, 24))
>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 173, 27))
>TestRecursiveReferenceToT : Symbol(TestRecursiveReferenceToT, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 167, 34))
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 173, 24))
>T : Symbol(T, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 173, 24))

const res = tstrcv({
>res : Symbol(res, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 175, 5))
>tstrcv : Symbol(tstrcv, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 171, 1))

ok: { a: "it's a string" } as const,
>ok : Symbol(ok, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 175, 20))
>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 176, 7))
>const : Symbol(const)

not_ok: { a: 123 },
>not_ok : Symbol(not_ok, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 176, 38))
>a : Symbol(a, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 177, 11))

})

res
>res : Symbol(res, Decl(reverseMappedTypeInferFromFilteringNameType2.ts, 175, 5))

// ^?
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,101 @@ const config2 = createXMachine({

});


type Bombolo<T> = {
>Bombolo : Bombolo<T>

[
K in keyof T as K extends 'enabled'
? K
: 'enabled' extends keyof T
? T["enabled"] extends true
>true : true

? K
: never
: never
]: T[K]
}


declare function bombolo<T>(a: Bombolo<T>): T
>bombolo : <T>(a: Bombolo<T>) => T
>a : Bombolo<T>

bombolo({ a: "a", b: "b", c: "c", enabled: false as const})
>bombolo({ a: "a", b: "b", c: "c", enabled: false as const}) : { enabled: false; }
>bombolo : <T>(a: Bombolo<T>) => T
>{ a: "a", b: "b", c: "c", enabled: false as const} : { a: string; b: string; c: string; enabled: false; }
>a : string
>"a" : "a"
>b : string
>"b" : "b"
>c : string
>"c" : "c"
>enabled : false
>false as const : false
>false : false

bombolo({ a: "a", b: "b", c: "c", enabled: true as const})
>bombolo({ a: "a", b: "b", c: "c", enabled: true as const}) : { a: string; b: string; c: string; enabled: true; }
>bombolo : <T>(a: Bombolo<T>) => T
>{ a: "a", b: "b", c: "c", enabled: true as const} : { a: string; b: string; c: string; enabled: true; }
>a : string
>"a" : "a"
>b : string
>"b" : "b"
>c : string
>"c" : "c"
>enabled : true
>true as const : true
>true : true

// no excess property check because the parameter type turns out to be the empty object type {}
bombolo({ a: "a", b: "b", c: "c"})
>bombolo({ a: "a", b: "b", c: "c"}) : {}
>bombolo : <T>(a: Bombolo<T>) => T
>{ a: "a", b: "b", c: "c"} : { a: string; b: string; c: string; }
>a : string
>"a" : "a"
>b : string
>"b" : "b"
>c : string
>"c" : "c"

type TestRecursiveReferenceToT<T> = {
>TestRecursiveReferenceToT : TestRecursiveReferenceToT<T>

[K in keyof T as T[K] extends string ? K : never ]: { a: T[K] }
>a : T[K]
}

declare function tstrcv<T>(a: TestRecursiveReferenceToT<T>): T
>tstrcv : <T>(a: TestRecursiveReferenceToT<T>) => T
>a : TestRecursiveReferenceToT<T>

const res = tstrcv({
>res : { ok: "it's a string"; }
>tstrcv({ ok: { a: "it's a string" } as const, not_ok: { a: 123 },}) : { ok: "it's a string"; }
>tstrcv : <T>(a: TestRecursiveReferenceToT<T>) => T
>{ ok: { a: "it's a string" } as const, not_ok: { a: 123 },} : { ok: { readonly a: "it's a string"; }; not_ok: { a: number; }; }

ok: { a: "it's a string" } as const,
>ok : { readonly a: "it's a string"; }
>{ a: "it's a string" } as const : { readonly a: "it's a string"; }
>{ a: "it's a string" } : { readonly a: "it's a string"; }
>a : "it's a string"
>"it's a string" : "it's a string"

not_ok: { a: 123 },
>not_ok : { a: number; }
>{ a: 123 } : { a: number; }
>a : number
>123 : 123

})

res
>res : { ok: "it's a string"; }

// ^?
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,39 @@ const config2 = createXMachine({
},
extra: 10,
});


type Bombolo<T> = {
[
K in keyof T as K extends 'enabled'
? K
: 'enabled' extends keyof T
? T["enabled"] extends true
? K
: never
: never
]: T[K]
}


declare function bombolo<T>(a: Bombolo<T>): T

bombolo({ a: "a", b: "b", c: "c", enabled: false as const})
bombolo({ a: "a", b: "b", c: "c", enabled: true as const})

// no excess property check because the parameter type turns out to be the empty object type {}
bombolo({ a: "a", b: "b", c: "c"})

type TestRecursiveReferenceToT<T> = {
[K in keyof T as T[K] extends string ? K : never ]: { a: T[K] }
}

declare function tstrcv<T>(a: TestRecursiveReferenceToT<T>): T

const res = tstrcv({
ok: { a: "it's a string" } as const,
not_ok: { a: 123 },
})

res
// ^?