From ae54ef827417d55bad801aeb075ccabe575b8092 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 1 Sep 2021 17:33:39 -0700 Subject: [PATCH 1/3] Add case for type related to for generic mapped type with as clause target --- src/compiler/checker.ts | 153 ++++++++++++++---- .../mapped/mappedTypeAsClauseRelationships.ts | 29 ++++ 2 files changed, 150 insertions(+), 32 deletions(-) create mode 100644 tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8ab4f678f4e87..5aece7d2881ed 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18583,46 +18583,133 @@ 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); - const modifiers = getMappedTypeModifiers(target); - if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { - if (template.flags & TypeFlags.IndexedAccess && (template as IndexedAccessType).objectType === source && - (template as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) { - return Ternary.True; - } - if (!isGenericMappedType(source)) { - const targetConstraint = getConstraintTypeFromMappedType(target); - 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. - if (includeOptional - ? !(filteredByApplicability!.flags & TypeFlags.Never) - : isRelatedTo(targetConstraint, sourceKeys)) { - const templateType = getTemplateTypeFromMappedType(target); - 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]` - const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable); - if (nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) { - if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, reportErrors)) { - return result; + else if (isGenericMappedType(target)) { + if (!target.declaration.nameType) { + // A source type T is related to a target type { [P in X]: T[P] } + const template = getTemplateTypeFromMappedType(target); + const modifiers = getMappedTypeModifiers(target); + // Why are we excluding the case where we have exclude optional modifiers? + if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { + // If: + // - template type has shape `T[R]` + // - template's object type `T` = `S` (source) + // - template's index type `R` = `P` (its parameter type) + // In other words: `S <: { [P in X]: S[P] }` + if (template.flags & TypeFlags.IndexedAccess && (template as IndexedAccessType).objectType === source && + (template as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) { + return Ternary.True; + } + // Otherwise, if `S` is not generic mapped type: + if (!isGenericMappedType(source)) { + // Constraint would be `X` in `{ [P in X]: ... }` + const targetConstraint = getConstraintTypeFromMappedType(target); + // Keys (properties) of type `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. + if (includeOptional + ? !(filteredByApplicability!.flags & TypeFlags.Never) + // Here we check that `Q <: keyof S` + : isRelatedTo(targetConstraint, sourceKeys)) { + const templateType = getTemplateTypeFromMappedType(target); + 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]` + const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable); + if (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; + // Get `S[P]` + const indexedAccessType = getIndexedAccessType(source, indexingType); + // Now we check if `S[P] <: X` + if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { + return result; + } } } - else { - const indexingType = filteredByApplicability ? getIntersectionType([filteredByApplicability, typeParameter]) : typeParameter; + originalErrorInfo = errorInfo; + resetErrorInfo(saveErrorInfo); + } + } + } + else { // Keys are remapped + // A source type T is related to a target type { [P in Q as R]: X } + // const template = getTemplateTypeFromMappedType(target); + const modifiers = getMappedTypeModifiers(target); + // TODO: Why are we excluding the case where we have exclude optional modifiers? + if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { + // >> Don't think this works if we have key remapping. + // >> There might be other simple common cases though. + // If: + // - template type has shape `T[R]` + // - template's object type `T` = `S` (source) + // - template's index type `R` = `P` (its parameter type) + // In other words: `S <: { [P in X]: S[P] }` + // if (template.flags & TypeFlags.IndexedAccess && (template as IndexedAccessType).objectType === source && + // (template as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) { + // return Ternary.True; + // } + // Otherwise, if `S` is not generic mapped type: + if (!isGenericMappedType(source)) { + // Target keys would be `R` in `{ [P in Q as R]: X }` + const targetKeys = getNameTypeFromMappedType(target)!; // Target has a name type + + // const targetConstraint = getConstraintTypeFromMappedType(target); + + // Keys of type `S` + const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true); + + // const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional; + // const filteredByApplicability = includeOptional ? intersectTypes(targetKeys, 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. + //>> TODO: 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. + // if (includeOptional + // ? !(filteredByApplicability!.flags & TypeFlags.Never) + // // Here we check that `Q <: keyof S` + // : isRelatedTo(targetConstraint, sourceKeys)) { + // const templateType = getTemplateTypeFromMappedType(target); + // 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]` + // const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable); + // if (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; + // // Get `S[P]` + // const indexedAccessType = getIndexedAccessType(source, indexingType); + // // Now we check if `S[P] <: X` + // if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { + // return result; + // } + // } + if (isRelatedTo(targetKeys, sourceKeys)) { + const templateType = getTemplateTypeFromMappedType(target); + // const typeParameter = getTypeParameterFromMappedType(target); + + const indexingType = targetKeys; // >> Is that right? + // Get `S[R]` const indexedAccessType = getIndexedAccessType(source, indexingType); + // >> That part fails: if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { return result; } } + originalErrorInfo = errorInfo; + resetErrorInfo(saveErrorInfo); } - originalErrorInfo = errorInfo; - resetErrorInfo(saveErrorInfo); } } } @@ -18722,6 +18809,7 @@ namespace ts { } } } + // >> HERE else if (source.flags & TypeFlags.Conditional) { if (target.flags & TypeFlags.Conditional) { // Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if @@ -18748,6 +18836,7 @@ namespace ts { } } } + // >> HERE: source is conditional, target is not else { // conditionals aren't related to one another via distributive constraint as it is much too inaccurate and allows way // more assignments than are desirable (since it maps the source check type to its constraint, it loses information) diff --git a/tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts b/tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts new file mode 100644 index 0000000000000..07ef901620e09 --- /dev/null +++ b/tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts @@ -0,0 +1,29 @@ +// #45212 + +// Original issue: +type Methods = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; +type H = T[keyof Methods]; // Should not error. + +// `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; // Should not error. + let y: Modify = val; // Should an 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; // Also ok + let z: FilterExclOpt = val; // Should error. + let w: ModifyExclOpt = val; // Should error. +} + + From f91648042b748d346f5d9560aa80b67f6da58b3e Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 2 Sep 2021 17:22:42 -0700 Subject: [PATCH 2/3] Clean up code and add baselines --- src/compiler/checker.ts | 170 ++++++------------ ...mappedTypeAsClauseRelationships.errors.txt | 40 +++++ .../mappedTypeAsClauseRelationships.js | 41 +++++ .../mappedTypeAsClauseRelationships.symbols | 142 +++++++++++++++ .../mappedTypeAsClauseRelationships.types | 64 +++++++ .../mapped/mappedTypeAsClauseRelationships.ts | 16 +- 6 files changed, 346 insertions(+), 127 deletions(-) create mode 100644 tests/baselines/reference/mappedTypeAsClauseRelationships.errors.txt create mode 100644 tests/baselines/reference/mappedTypeAsClauseRelationships.js create mode 100644 tests/baselines/reference/mappedTypeAsClauseRelationships.symbols create mode 100644 tests/baselines/reference/mappedTypeAsClauseRelationships.types diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5aece7d2881ed..67df71ca1bb01 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18584,132 +18584,66 @@ namespace ts { } } else if (isGenericMappedType(target)) { - if (!target.declaration.nameType) { - // A source type T is related to a target type { [P in X]: T[P] } - const template = getTemplateTypeFromMappedType(target); - const modifiers = getMappedTypeModifiers(target); - // Why are we excluding the case where we have exclude optional modifiers? - if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { - // If: - // - template type has shape `T[R]` - // - template's object type `T` = `S` (source) - // - template's index type `R` = `P` (its parameter type) - // In other words: `S <: { [P in X]: S[P] }` - if (template.flags & TypeFlags.IndexedAccess && (template as IndexedAccessType).objectType === source && - (template as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) { - return Ternary.True; - } - // Otherwise, if `S` is not generic mapped type: - if (!isGenericMappedType(source)) { - // Constraint would be `X` in `{ [P in X]: ... }` - const targetConstraint = getConstraintTypeFromMappedType(target); - // Keys (properties) of type `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. - if (includeOptional - ? !(filteredByApplicability!.flags & TypeFlags.Never) - // Here we check that `Q <: keyof S` - : isRelatedTo(targetConstraint, sourceKeys)) { - const templateType = getTemplateTypeFromMappedType(target); - 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]` - const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable); - if (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; - // Get `S[P]` - const indexedAccessType = getIndexedAccessType(source, indexingType); - // Now we check if `S[P] <: X` - if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { - return result; - } + // 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 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)) { + // 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(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(targetKeys, sourceKeys)) { + const typeParameter = getTypeParameterFromMappedType(target); + + // 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 (!keysRemapped && nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) { + if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, reportErrors)) { + return result; } } - originalErrorInfo = errorInfo; - resetErrorInfo(saveErrorInfo); - } - } - } - else { // Keys are remapped - // A source type T is related to a target type { [P in Q as R]: X } - // const template = getTemplateTypeFromMappedType(target); - const modifiers = getMappedTypeModifiers(target); - // TODO: Why are we excluding the case where we have exclude optional modifiers? - if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { - // >> Don't think this works if we have key remapping. - // >> There might be other simple common cases though. - // If: - // - template type has shape `T[R]` - // - template's object type `T` = `S` (source) - // - template's index type `R` = `P` (its parameter type) - // In other words: `S <: { [P in X]: S[P] }` - // if (template.flags & TypeFlags.IndexedAccess && (template as IndexedAccessType).objectType === source && - // (template as IndexedAccessType).indexType === getTypeParameterFromMappedType(target)) { - // return Ternary.True; - // } - // Otherwise, if `S` is not generic mapped type: - if (!isGenericMappedType(source)) { - // Target keys would be `R` in `{ [P in Q as R]: X }` - const targetKeys = getNameTypeFromMappedType(target)!; // Target has a name type - - // const targetConstraint = getConstraintTypeFromMappedType(target); - - // Keys of type `S` - const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true); - - // const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional; - // const filteredByApplicability = includeOptional ? intersectTypes(targetKeys, 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. - //>> TODO: 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. - // if (includeOptional - // ? !(filteredByApplicability!.flags & TypeFlags.Never) - // // Here we check that `Q <: keyof S` - // : isRelatedTo(targetConstraint, sourceKeys)) { - // const templateType = getTemplateTypeFromMappedType(target); - // 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]` - // const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable); - // if (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; - // // Get `S[P]` - // const indexedAccessType = getIndexedAccessType(source, indexingType); - // // Now we check if `S[P] <: X` - // if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { - // return result; - // } - // } - if (isRelatedTo(targetKeys, sourceKeys)) { - const templateType = getTemplateTypeFromMappedType(target); - // const typeParameter = getTypeParameterFromMappedType(target); - - const indexingType = targetKeys; // >> Is that right? - // Get `S[R]` + else { + // 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); - // >> That part fails: + // 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; } } - originalErrorInfo = errorInfo; - resetErrorInfo(saveErrorInfo); } + originalErrorInfo = errorInfo; + resetErrorInfo(saveErrorInfo); } } } 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 index 07ef901620e09..051192159e131 100644 --- a/tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts +++ b/tests/cases/conformance/types/mapped/mappedTypeAsClauseRelationships.ts @@ -1,8 +1,6 @@ -// #45212 - -// Original issue: +// From original issue #45212: type Methods = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; -type H = T[keyof Methods]; // Should not error. +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] }; @@ -10,8 +8,8 @@ type Filter = { [P in keyof T as T[P] extends Function ? P : never]: T[P] }; type Modify = { [P in keyof T as P extends string? `bool${P}`: P]: T[P] }; function fun(val: T) { - let x: Filter = val; // Should not error. - let y: Modify = val; // Should an error. + 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] }; @@ -21,9 +19,9 @@ type ModifyExclOpt = { [P in keyof T as P extends string? `bool${P}`: never ] function fun2(val: T) { let x: FilterInclOpt = val; // Ok - let y: ModifyInclOpt = val; // Also ok - let z: FilterExclOpt = val; // Should error. - let w: ModifyExclOpt = val; // Should error. + let y: ModifyInclOpt = val; // Ok + let z: FilterExclOpt = val; // Error + let w: ModifyExclOpt = val; // Error } From ca98987f798fa21fd5bb120bfacd4654645123d0 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 2 Sep 2021 18:17:52 -0700 Subject: [PATCH 3/3] cleanup --- src/compiler/checker.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e3cfc2345ea83..6233b59dd7a47 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18745,7 +18745,6 @@ namespace ts { } } } - // >> HERE else if (source.flags & TypeFlags.Conditional) { if (target.flags & TypeFlags.Conditional) { // Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if @@ -18772,7 +18771,6 @@ namespace ts { } } } - // >> HERE: source is conditional, target is not else { // conditionals aren't related to one another via distributive constraint as it is much too inaccurate and allows way // more assignments than are desirable (since it maps the source check type to its constraint, it loses information)