diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 186422e5a90c0..6233b59dd7a47 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18585,39 +18585,60 @@ namespace ts { originalErrorInfo = undefined; } } - else if (isGenericMappedType(target) && !target.declaration.nameType) { - // A source type T is related to a target type { [P in X]: T[P] } - const template = getTemplateTypeFromMappedType(target); + else if (isGenericMappedType(target)) { + // Check if source type `S` is related to target type `{ [P in Q]: T }` or `{ [P in Q as R]: T}`. + const keysRemapped = !!target.declaration.nameType; + const templateType = getTemplateTypeFromMappedType(target); const modifiers = getMappedTypeModifiers(target); if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { - if (template.flags & TypeFlags.IndexedAccess && (template as IndexedAccessType).objectType === source && - (template as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) { + // If the mapped type has shape `{ [P in Q]: T[P] }`, + // source `S` is related to target if `T` = `S`, i.e. `S` is related to `{ [P in Q]: S[P] }`. + if (!keysRemapped && templateType.flags & TypeFlags.IndexedAccess && (templateType as IndexedAccessType).objectType === source && + (templateType as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) { return Ternary.True; } if (!isGenericMappedType(source)) { - const targetConstraint = getConstraintTypeFromMappedType(target); + // If target has shape `{ [P in Q as R]: T}`, then its keys have type `R`. + // If target has shape `{ [P in Q]: T }`, then its keys have type `Q`. + const targetKeys = keysRemapped ? getNameTypeFromMappedType(target)! : getConstraintTypeFromMappedType(target); + // Type of the keys of source type `S`, i.e. `keyof S`. const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true); const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional; - const filteredByApplicability = includeOptional ? intersectTypes(targetConstraint, sourceKeys) : undefined; - // A source type T is related to a target type { [P in Q]: X } if Q is related to keyof T and T[Q] is related to X. - // A source type T is related to a target type { [P in Q]?: X } if some constituent Q' of Q is related to keyof T and T[Q'] is related to X. + const filteredByApplicability = includeOptional ? intersectTypes(targetKeys, sourceKeys) : undefined; + // A source type `S` is related to a target type `{ [P in Q]: T }` if `Q` is related to `keyof S` and `S[Q]` is related to `T`. + // A source type `S` is related to a target type `{ [P in Q as R]: T }` if `R` is related to `keyof S` and `S[R]` is related to `T. + // A source type `S` is related to a target type `{ [P in Q]?: T }` if some constituent `Q'` of `Q` is related to `keyof S` and `S[Q']` is related to `T`. + // A source type `S` is related to a target type `{ [P in Q as R]?: T }` if some constituent `R'` of `R` is related to `keyof S` and `S[R']` is related to `T`. if (includeOptional ? !(filteredByApplicability!.flags & TypeFlags.Never) - : isRelatedTo(targetConstraint, sourceKeys)) { - const templateType = getTemplateTypeFromMappedType(target); + : isRelatedTo(targetKeys, sourceKeys)) { const typeParameter = getTypeParameterFromMappedType(target); - // Fastpath: When the template has the form Obj[P] where P is the mapped type parameter, directly compare `source` with `Obj` - // to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `source[P]` + // Fastpath: When the template type has the form `Obj[P]` where `P` is the mapped type parameter, directly compare source `S` with `Obj` + // to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `S[P]`. const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable); - if (nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) { + if (!keysRemapped && nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) { if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, reportErrors)) { return result; } } else { - const indexingType = filteredByApplicability ? getIntersectionType([filteredByApplicability, typeParameter]) : typeParameter; + // We need to compare the type of a property on the source type `S` to the type of the same property on the target type, + // so we need to construct an indexing type representing a property, and then use indexing type to index the source type for comparison. + + // If the target type has shape `{ [P in Q]: T }`, then a property of the target has type `P`. + // If the target type has shape `{ [P in Q]?: T }`, then a property of the target has type `P`, + // but the property is optional, so we only want to compare properties `P` that are common between `keyof S` and `Q`. + // If the target type has shape `{ [P in Q as R]: T }`, then a property of the target has type `R`. + // If the target type has shape `{ [P in Q as R]?: T }`, then a property of the target has type `R`, + // but the property is optional, so we only want to compare properties `R` that are common between `keyof S` and `R`. + const indexingType = keysRemapped + ? (filteredByApplicability || targetKeys) + : filteredByApplicability + ? getIntersectionType([filteredByApplicability, typeParameter]) + : typeParameter; const indexedAccessType = getIndexedAccessType(source, indexingType); + // Compare `S[indexingType]` to `T`, where `T` is the type of a property of the target type. if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { return result; } diff --git a/tests/baselines/reference/mappedTypeAsClauseRelationships.errors.txt b/tests/baselines/reference/mappedTypeAsClauseRelationships.errors.txt new file mode 100644 index 0000000000000..98b164a866a27 --- /dev/null +++ b/tests/baselines/reference/mappedTypeAsClauseRelationships.errors.txt @@ -0,0 +1,40 @@ +tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts(12,9): error TS2322: Type 'T' is not assignable to type 'Modify'. +tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts(23,9): error TS2322: Type 'T' is not assignable to type 'FilterExclOpt'. +tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts(24,9): error TS2322: Type 'T' is not assignable to type 'ModifyExclOpt'. + + +==== tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts (3 errors) ==== + // From original issue #45212: + type Methods = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; + type H = T[keyof Methods]; // Ok + + // `Filter` only filters out some keys of `T`. + type Filter = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; + // `Modify` might modify some keys of `T`. + type Modify = { [P in keyof T as P extends string? `bool${P}`: P]: T[P] }; + + function fun(val: T) { + let x: Filter = val; // Ok + let y: Modify = val; // Error + ~ +!!! error TS2322: Type 'T' is not assignable to type 'Modify'. + } + + type FilterInclOpt = { [P in keyof T as T[P] extends Function ? P : never]+?: T[P] }; + type ModifyInclOpt = { [P in keyof T as P extends string? `bool${P}`: never ]+?: T[P] }; + type FilterExclOpt = { [P in keyof T as T[P] extends Function ? P : never]-?: T[P] }; + type ModifyExclOpt = { [P in keyof T as P extends string? `bool${P}`: never ]-?: T[P] }; + + function fun2(val: T) { + let x: FilterInclOpt = val; // Ok + let y: ModifyInclOpt = val; // Ok + let z: FilterExclOpt = val; // Error + ~ +!!! error TS2322: Type 'T' is not assignable to type 'FilterExclOpt'. + let w: ModifyExclOpt = val; // Error + ~ +!!! error TS2322: Type 'T' is not assignable to type 'ModifyExclOpt'. + } + + + \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeAsClauseRelationships.js b/tests/baselines/reference/mappedTypeAsClauseRelationships.js new file mode 100644 index 0000000000000..a1b0f68f9c2b5 --- /dev/null +++ b/tests/baselines/reference/mappedTypeAsClauseRelationships.js @@ -0,0 +1,41 @@ +//// [mappedTypeAsClauseRelationships.ts] +// From original issue #45212: +type Methods = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; +type H = T[keyof Methods]; // Ok + +// `Filter` only filters out some keys of `T`. +type Filter = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; +// `Modify` might modify some keys of `T`. +type Modify = { [P in keyof T as P extends string? `bool${P}`: P]: T[P] }; + +function fun(val: T) { + let x: Filter = val; // Ok + let y: Modify = val; // Error +} + +type FilterInclOpt = { [P in keyof T as T[P] extends Function ? P : never]+?: T[P] }; +type ModifyInclOpt = { [P in keyof T as P extends string? `bool${P}`: never ]+?: T[P] }; +type FilterExclOpt = { [P in keyof T as T[P] extends Function ? P : never]-?: T[P] }; +type ModifyExclOpt = { [P in keyof T as P extends string? `bool${P}`: never ]-?: T[P] }; + +function fun2(val: T) { + let x: FilterInclOpt = val; // Ok + let y: ModifyInclOpt = val; // Ok + let z: FilterExclOpt = val; // Error + let w: ModifyExclOpt = val; // Error +} + + + + +//// [mappedTypeAsClauseRelationships.js] +function fun(val) { + var x = val; // Ok + var y = val; // Error +} +function fun2(val) { + var x = val; // Ok + var y = val; // Ok + var z = val; // Error + var w = val; // Error +} diff --git a/tests/baselines/reference/mappedTypeAsClauseRelationships.symbols b/tests/baselines/reference/mappedTypeAsClauseRelationships.symbols new file mode 100644 index 0000000000000..629115cbd07f6 --- /dev/null +++ b/tests/baselines/reference/mappedTypeAsClauseRelationships.symbols @@ -0,0 +1,142 @@ +=== tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts === +// From original issue #45212: +type Methods = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; +>Methods : Symbol(Methods, Decl(mappedTypeAsClauseRelationships.ts, 0, 0)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 1, 13)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 1, 21)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 1, 13)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 1, 13)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 1, 21)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 1, 21)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 1, 13)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 1, 21)) + +type H = T[keyof Methods]; // Ok +>H : Symbol(H, Decl(mappedTypeAsClauseRelationships.ts, 1, 80)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 2, 7)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 2, 7)) +>Methods : Symbol(Methods, Decl(mappedTypeAsClauseRelationships.ts, 0, 0)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 2, 7)) + +// `Filter` only filters out some keys of `T`. +type Filter = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; +>Filter : Symbol(Filter, Decl(mappedTypeAsClauseRelationships.ts, 2, 32)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 5, 12)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 5, 20)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 5, 12)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 5, 12)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 5, 20)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 5, 20)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 5, 12)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 5, 20)) + +// `Modify` might modify some keys of `T`. +type Modify = { [P in keyof T as P extends string? `bool${P}`: P]: T[P] }; +>Modify : Symbol(Modify, Decl(mappedTypeAsClauseRelationships.ts, 5, 79)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 7, 12)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 7, 12)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 7, 12)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 7, 20)) + +function fun(val: T) { +>fun : Symbol(fun, Decl(mappedTypeAsClauseRelationships.ts, 7, 77)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 9, 13)) +>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 9, 16)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 9, 13)) + + let x: Filter = val; // Ok +>x : Symbol(x, Decl(mappedTypeAsClauseRelationships.ts, 10, 7)) +>Filter : Symbol(Filter, Decl(mappedTypeAsClauseRelationships.ts, 2, 32)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 9, 13)) +>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 9, 16)) + + let y: Modify = val; // Error +>y : Symbol(y, Decl(mappedTypeAsClauseRelationships.ts, 11, 7)) +>Modify : Symbol(Modify, Decl(mappedTypeAsClauseRelationships.ts, 5, 79)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 9, 13)) +>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 9, 16)) +} + +type FilterInclOpt = { [P in keyof T as T[P] extends Function ? P : never]+?: T[P] }; +>FilterInclOpt : Symbol(FilterInclOpt, Decl(mappedTypeAsClauseRelationships.ts, 12, 1)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 14, 19)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 14, 27)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 14, 19)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 14, 19)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 14, 27)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 14, 27)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 14, 19)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 14, 27)) + +type ModifyInclOpt = { [P in keyof T as P extends string? `bool${P}`: never ]+?: T[P] }; +>ModifyInclOpt : Symbol(ModifyInclOpt, Decl(mappedTypeAsClauseRelationships.ts, 14, 88)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 15, 19)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 15, 27)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 15, 19)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 15, 27)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 15, 27)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 15, 19)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 15, 27)) + +type FilterExclOpt = { [P in keyof T as T[P] extends Function ? P : never]-?: T[P] }; +>FilterExclOpt : Symbol(FilterExclOpt, Decl(mappedTypeAsClauseRelationships.ts, 15, 91)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 16, 19)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 16, 27)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 16, 19)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 16, 19)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 16, 27)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 16, 27)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 16, 19)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 16, 27)) + +type ModifyExclOpt = { [P in keyof T as P extends string? `bool${P}`: never ]-?: T[P] }; +>ModifyExclOpt : Symbol(ModifyExclOpt, Decl(mappedTypeAsClauseRelationships.ts, 16, 88)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 17, 19)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 17, 27)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 17, 19)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 17, 27)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 17, 27)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 17, 19)) +>P : Symbol(P, Decl(mappedTypeAsClauseRelationships.ts, 17, 27)) + +function fun2(val: T) { +>fun2 : Symbol(fun2, Decl(mappedTypeAsClauseRelationships.ts, 17, 91)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14)) +>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14)) + + let x: FilterInclOpt = val; // Ok +>x : Symbol(x, Decl(mappedTypeAsClauseRelationships.ts, 20, 7)) +>FilterInclOpt : Symbol(FilterInclOpt, Decl(mappedTypeAsClauseRelationships.ts, 12, 1)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14)) +>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17)) + + let y: ModifyInclOpt = val; // Ok +>y : Symbol(y, Decl(mappedTypeAsClauseRelationships.ts, 21, 7)) +>ModifyInclOpt : Symbol(ModifyInclOpt, Decl(mappedTypeAsClauseRelationships.ts, 14, 88)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14)) +>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17)) + + let z: FilterExclOpt = val; // Error +>z : Symbol(z, Decl(mappedTypeAsClauseRelationships.ts, 22, 7)) +>FilterExclOpt : Symbol(FilterExclOpt, Decl(mappedTypeAsClauseRelationships.ts, 15, 91)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14)) +>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17)) + + let w: ModifyExclOpt = val; // Error +>w : Symbol(w, Decl(mappedTypeAsClauseRelationships.ts, 23, 7)) +>ModifyExclOpt : Symbol(ModifyExclOpt, Decl(mappedTypeAsClauseRelationships.ts, 16, 88)) +>T : Symbol(T, Decl(mappedTypeAsClauseRelationships.ts, 19, 14)) +>val : Symbol(val, Decl(mappedTypeAsClauseRelationships.ts, 19, 17)) +} + + + diff --git a/tests/baselines/reference/mappedTypeAsClauseRelationships.types b/tests/baselines/reference/mappedTypeAsClauseRelationships.types new file mode 100644 index 0000000000000..4136aab7cff07 --- /dev/null +++ b/tests/baselines/reference/mappedTypeAsClauseRelationships.types @@ -0,0 +1,64 @@ +=== tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts === +// From original issue #45212: +type Methods = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; +>Methods : Methods + +type H = T[keyof Methods]; // Ok +>H : H + +// `Filter` only filters out some keys of `T`. +type Filter = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; +>Filter : Filter + +// `Modify` might modify some keys of `T`. +type Modify = { [P in keyof T as P extends string? `bool${P}`: P]: T[P] }; +>Modify : Modify + +function fun(val: T) { +>fun : (val: T) => void +>val : T + + let x: Filter = val; // Ok +>x : Filter +>val : T + + let y: Modify = val; // Error +>y : Modify +>val : T +} + +type FilterInclOpt = { [P in keyof T as T[P] extends Function ? P : never]+?: T[P] }; +>FilterInclOpt : FilterInclOpt + +type ModifyInclOpt = { [P in keyof T as P extends string? `bool${P}`: never ]+?: T[P] }; +>ModifyInclOpt : ModifyInclOpt + +type FilterExclOpt = { [P in keyof T as T[P] extends Function ? P : never]-?: T[P] }; +>FilterExclOpt : FilterExclOpt + +type ModifyExclOpt = { [P in keyof T as P extends string? `bool${P}`: never ]-?: T[P] }; +>ModifyExclOpt : ModifyExclOpt + +function fun2(val: T) { +>fun2 : (val: T) => void +>val : T + + let x: FilterInclOpt = val; // Ok +>x : FilterInclOpt +>val : T + + let y: ModifyInclOpt = val; // Ok +>y : ModifyInclOpt +>val : T + + let z: FilterExclOpt = val; // Error +>z : FilterExclOpt +>val : T + + let w: ModifyExclOpt = val; // Error +>w : ModifyExclOpt +>val : T +} + + + diff --git a/tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts b/tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts new file mode 100644 index 0000000000000..051192159e131 --- /dev/null +++ b/tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts @@ -0,0 +1,27 @@ +// From original issue #45212: +type Methods = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; +type H = T[keyof Methods]; // Ok + +// `Filter` only filters out some keys of `T`. +type Filter = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; +// `Modify` might modify some keys of `T`. +type Modify = { [P in keyof T as P extends string? `bool${P}`: P]: T[P] }; + +function fun(val: T) { + let x: Filter = val; // Ok + let y: Modify = val; // Error +} + +type FilterInclOpt = { [P in keyof T as T[P] extends Function ? P : never]+?: T[P] }; +type ModifyInclOpt = { [P in keyof T as P extends string? `bool${P}`: never ]+?: T[P] }; +type FilterExclOpt = { [P in keyof T as T[P] extends Function ? P : never]-?: T[P] }; +type ModifyExclOpt = { [P in keyof T as P extends string? `bool${P}`: never ]-?: T[P] }; + +function fun2(val: T) { + let x: FilterInclOpt = val; // Ok + let y: ModifyInclOpt = val; // Ok + let z: FilterExclOpt = val; // Error + let w: ModifyExclOpt = val; // Error +} + +