diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c2f40c169dc3f..4b824e8825dbb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5765,12 +5765,13 @@ namespace ts { return type.modifiersType; } + function isPartialMappedType(type: Type) { + return getObjectFlags(type) & ObjectFlags.Mapped && !!(type).declaration.questionToken; + } + function isGenericMappedType(type: Type) { - if (getObjectFlags(type) & ObjectFlags.Mapped) { - const constraintType = getConstraintTypeFromMappedType(type); - return maybeTypeOfKind(constraintType, TypeFlags.TypeVariable | TypeFlags.Index); - } - return false; + return getObjectFlags(type) & ObjectFlags.Mapped && + maybeTypeOfKind(getConstraintTypeFromMappedType(type), TypeFlags.TypeVariable | TypeFlags.Index); } function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { @@ -9254,8 +9255,12 @@ namespace ts { if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { // Report structural errors only if we haven't reported any errors yet const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !sourceIsPrimitive; - if (isGenericMappedType(source) || isGenericMappedType(target)) { - result = mappedTypeRelatedTo(source, target, reportStructuralErrors); + // An empty object type is related to any mapped type that includes a '?' modifier. + if (isPartialMappedType(target) && !isGenericMappedType(source) && isEmptyObjectType(source)) { + result = Ternary.True; + } + else if (isGenericMappedType(target)) { + result = isGenericMappedType(source) ? mappedTypeRelatedTo(source, target, reportStructuralErrors) : Ternary.False; } else { result = propertiesRelatedTo(source, target, reportStructuralErrors); @@ -9284,33 +9289,19 @@ namespace ts { // A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is // related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice // that S and T are contra-variant whereas X and Y are co-variant. - function mappedTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary { - if (isGenericMappedType(target)) { - if (isGenericMappedType(source)) { - const sourceReadonly = !!(source).declaration.readonlyToken; - const sourceOptional = !!(source).declaration.questionToken; - const targetReadonly = !!(target).declaration.readonlyToken; - const targetOptional = !!(target).declaration.questionToken; - const modifiersRelated = relation === identityRelation ? - sourceReadonly === targetReadonly && sourceOptional === targetOptional : - relation === comparableRelation || !sourceOptional || targetOptional; - if (modifiersRelated) { - let result: Ternary; - if (result = isRelatedTo(getConstraintTypeFromMappedType(target), getConstraintTypeFromMappedType(source), reportErrors)) { - const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]); - return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), reportErrors); - } - } - } - else if ((target).declaration.questionToken && isEmptyObjectType(source)) { - return Ternary.True; - - } - } - else if (relation !== identityRelation) { - const resolved = resolveStructuredTypeMembers(target); - if (isEmptyResolvedType(resolved) || resolved.stringIndexInfo && resolved.stringIndexInfo.type.flags & TypeFlags.Any) { - return Ternary.True; + function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary { + const sourceReadonly = !!source.declaration.readonlyToken; + const sourceOptional = !!source.declaration.questionToken; + const targetReadonly = !!target.declaration.readonlyToken; + const targetOptional = !!target.declaration.questionToken; + const modifiersRelated = relation === identityRelation ? + sourceReadonly === targetReadonly && sourceOptional === targetOptional : + relation === comparableRelation || !sourceOptional || targetOptional; + if (modifiersRelated) { + let result: Ternary; + if (result = isRelatedTo(getConstraintTypeFromMappedType(target), getConstraintTypeFromMappedType(source), reportErrors)) { + const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]); + return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), reportErrors); } } return Ternary.False; diff --git a/tests/baselines/reference/mappedTypePartialConstraints.js b/tests/baselines/reference/mappedTypePartialConstraints.js new file mode 100644 index 0000000000000..1c83e2506dd0c --- /dev/null +++ b/tests/baselines/reference/mappedTypePartialConstraints.js @@ -0,0 +1,46 @@ +//// [mappedTypePartialConstraints.ts] +// Repro from #16985 + +interface MyInterface { + something: number; +} + +class MyClass { + doIt(data : Partial) {} +} + +class MySubClass extends MyClass {} + +function fn(arg: typeof MyClass) {}; + +fn(MySubClass); + + +//// [mappedTypePartialConstraints.js] +// Repro from #16985 +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var MyClass = (function () { + function MyClass() { + } + MyClass.prototype.doIt = function (data) { }; + return MyClass; +}()); +var MySubClass = (function (_super) { + __extends(MySubClass, _super); + function MySubClass() { + return _super !== null && _super.apply(this, arguments) || this; + } + return MySubClass; +}(MyClass)); +function fn(arg) { } +; +fn(MySubClass); diff --git a/tests/baselines/reference/mappedTypePartialConstraints.symbols b/tests/baselines/reference/mappedTypePartialConstraints.symbols new file mode 100644 index 0000000000000..7601cd68d6913 --- /dev/null +++ b/tests/baselines/reference/mappedTypePartialConstraints.symbols @@ -0,0 +1,36 @@ +=== tests/cases/compiler/mappedTypePartialConstraints.ts === +// Repro from #16985 + +interface MyInterface { +>MyInterface : Symbol(MyInterface, Decl(mappedTypePartialConstraints.ts, 0, 0)) + + something: number; +>something : Symbol(MyInterface.something, Decl(mappedTypePartialConstraints.ts, 2, 23)) +} + +class MyClass { +>MyClass : Symbol(MyClass, Decl(mappedTypePartialConstraints.ts, 4, 1)) +>T : Symbol(T, Decl(mappedTypePartialConstraints.ts, 6, 14)) +>MyInterface : Symbol(MyInterface, Decl(mappedTypePartialConstraints.ts, 0, 0)) + + doIt(data : Partial) {} +>doIt : Symbol(MyClass.doIt, Decl(mappedTypePartialConstraints.ts, 6, 38)) +>data : Symbol(data, Decl(mappedTypePartialConstraints.ts, 7, 7)) +>Partial : Symbol(Partial, Decl(lib.d.ts, --, --)) +>T : Symbol(T, Decl(mappedTypePartialConstraints.ts, 6, 14)) +} + +class MySubClass extends MyClass {} +>MySubClass : Symbol(MySubClass, Decl(mappedTypePartialConstraints.ts, 8, 1)) +>MyClass : Symbol(MyClass, Decl(mappedTypePartialConstraints.ts, 4, 1)) +>MyInterface : Symbol(MyInterface, Decl(mappedTypePartialConstraints.ts, 0, 0)) + +function fn(arg: typeof MyClass) {}; +>fn : Symbol(fn, Decl(mappedTypePartialConstraints.ts, 10, 48)) +>arg : Symbol(arg, Decl(mappedTypePartialConstraints.ts, 12, 12)) +>MyClass : Symbol(MyClass, Decl(mappedTypePartialConstraints.ts, 4, 1)) + +fn(MySubClass); +>fn : Symbol(fn, Decl(mappedTypePartialConstraints.ts, 10, 48)) +>MySubClass : Symbol(MySubClass, Decl(mappedTypePartialConstraints.ts, 8, 1)) + diff --git a/tests/baselines/reference/mappedTypePartialConstraints.types b/tests/baselines/reference/mappedTypePartialConstraints.types new file mode 100644 index 0000000000000..a0543774692e8 --- /dev/null +++ b/tests/baselines/reference/mappedTypePartialConstraints.types @@ -0,0 +1,37 @@ +=== tests/cases/compiler/mappedTypePartialConstraints.ts === +// Repro from #16985 + +interface MyInterface { +>MyInterface : MyInterface + + something: number; +>something : number +} + +class MyClass { +>MyClass : MyClass +>T : T +>MyInterface : MyInterface + + doIt(data : Partial) {} +>doIt : (data: Partial) => void +>data : Partial +>Partial : Partial +>T : T +} + +class MySubClass extends MyClass {} +>MySubClass : MySubClass +>MyClass : MyClass +>MyInterface : MyInterface + +function fn(arg: typeof MyClass) {}; +>fn : (arg: typeof MyClass) => void +>arg : typeof MyClass +>MyClass : typeof MyClass + +fn(MySubClass); +>fn(MySubClass) : void +>fn : (arg: typeof MyClass) => void +>MySubClass : typeof MySubClass + diff --git a/tests/baselines/reference/mappedTypeRelationships.errors.txt b/tests/baselines/reference/mappedTypeRelationships.errors.txt index c54330e9b1677..bc0aa32b14916 100644 --- a/tests/baselines/reference/mappedTypeRelationships.errors.txt +++ b/tests/baselines/reference/mappedTypeRelationships.errors.txt @@ -60,25 +60,26 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(51,5): error TS2 tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(56,5): error TS2542: Index signature in type 'Readonly' only permits reading. tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(61,5): error TS2542: Index signature in type 'Readonly' only permits reading. tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(66,5): error TS2542: Index signature in type 'Readonly' only permits reading. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(70,5): error TS2322: Type 'Partial' is not assignable to type 'T'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(75,5): error TS2322: Type 'Partial' is not assignable to type 'T'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(125,5): error TS2322: Type 'Partial' is not assignable to type 'Identity'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(141,5): error TS2322: Type '{ [P in keyof T]: T[P]; }' is not assignable to type '{ [P in keyof T]: U[P]; }'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(72,5): error TS2322: Type 'Partial' is not assignable to type 'T'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(78,5): error TS2322: Type 'Partial' is not assignable to type 'Partial'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(88,5): error TS2322: Type 'Readonly' is not assignable to type 'Readonly'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(127,5): error TS2322: Type 'Partial' is not assignable to type 'Identity'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(143,5): error TS2322: Type '{ [P in keyof T]: T[P]; }' is not assignable to type '{ [P in keyof T]: U[P]; }'. Type 'T[P]' is not assignable to type 'U[P]'. Type 'T[string]' is not assignable to type 'U[P]'. Type 'T[string]' is not assignable to type 'U[string]'. Type 'T[P]' is not assignable to type 'U[string]'. Type 'T[string]' is not assignable to type 'U[string]'. Type 'T' is not assignable to type 'U'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(146,5): error TS2322: Type '{ [P in keyof T]: T[P]; }' is not assignable to type '{ [P in keyof U]: U[P]; }'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(148,5): error TS2322: Type '{ [P in keyof T]: T[P]; }' is not assignable to type '{ [P in keyof U]: U[P]; }'. Type 'keyof U' is not assignable to type 'keyof T'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(151,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof T]: T[P]; }'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(153,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof T]: T[P]; }'. Type 'keyof T' is not assignable to type 'K'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(156,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof U]: U[P]; }'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(158,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof U]: U[P]; }'. Type 'keyof U' is not assignable to type 'K'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(161,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof T]: U[P]; }'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(163,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in keyof T]: U[P]; }'. Type 'keyof T' is not assignable to type 'K'. -tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(166,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in K]: U[P]; }'. +tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(168,5): error TS2322: Type '{ [P in K]: T[P]; }' is not assignable to type '{ [P in K]: U[P]; }'. Type 'T[P]' is not assignable to type 'U[P]'. Type 'T[string]' is not assignable to type 'U[P]'. Type 'T[string]' is not assignable to type 'U[string]'. @@ -87,7 +88,7 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(166,5): error TS Type 'T' is not assignable to type 'U'. -==== tests/cases/conformance/types/mapped/mappedTypeRelationships.ts (27 errors) ==== +==== tests/cases/conformance/types/mapped/mappedTypeRelationships.ts (28 errors) ==== function f1(x: T, k: keyof T) { return x[k]; } @@ -236,6 +237,8 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(166,5): error TS !!! error TS2542: Index signature in type 'Readonly' only permits reading. } + type Thing = { a: string, b: string }; + function f30(x: T, y: Partial) { x = y; // Error ~ @@ -243,11 +246,11 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(166,5): error TS y = x; } - function f31(x: T, y: Partial) { - x = y; // Error + function f31(x: Partial, y: Partial) { + x = y; + y = x; // Error ~ -!!! error TS2322: Type 'Partial' is not assignable to type 'T'. - y = x; +!!! error TS2322: Type 'Partial' is not assignable to type 'Partial'. } function f40(x: T, y: Readonly) { @@ -255,9 +258,11 @@ tests/cases/conformance/types/mapped/mappedTypeRelationships.ts(166,5): error TS y = x; } - function f41(x: T, y: Readonly) { + function f41(x: Readonly, y: Readonly) { x = y; - y = x; + y = x; // Error + ~ +!!! error TS2322: Type 'Readonly' is not assignable to type 'Readonly'. } type Item = { diff --git a/tests/baselines/reference/mappedTypeRelationships.js b/tests/baselines/reference/mappedTypeRelationships.js index 3580007137ea5..180c293b6c18b 100644 --- a/tests/baselines/reference/mappedTypeRelationships.js +++ b/tests/baselines/reference/mappedTypeRelationships.js @@ -67,14 +67,16 @@ function f23(x: T, y: Readonly, k: K) { y[k] = x[k]; // Error } +type Thing = { a: string, b: string }; + function f30(x: T, y: Partial) { x = y; // Error y = x; } -function f31(x: T, y: Partial) { - x = y; // Error - y = x; +function f31(x: Partial, y: Partial) { + x = y; + y = x; // Error } function f40(x: T, y: Readonly) { @@ -82,9 +84,9 @@ function f40(x: T, y: Readonly) { y = x; } -function f41(x: T, y: Readonly) { +function f41(x: Readonly, y: Readonly) { x = y; - y = x; + y = x; // Error } type Item = { @@ -228,8 +230,8 @@ function f30(x, y) { y = x; } function f31(x, y) { - x = y; // Error - y = x; + x = y; + y = x; // Error } function f40(x, y) { x = y; @@ -237,7 +239,7 @@ function f40(x, y) { } function f41(x, y) { x = y; - y = x; + y = x; // Error } function f50(obj, key) { var item = obj[key]; @@ -304,10 +306,14 @@ declare function f20(x: T, y: Readonly, k: keyof T): void; declare function f21(x: T, y: Readonly, k: K): void; declare function f22(x: T, y: Readonly, k: keyof T): void; declare function f23(x: T, y: Readonly, k: K): void; +declare type Thing = { + a: string; + b: string; +}; declare function f30(x: T, y: Partial): void; -declare function f31(x: T, y: Partial): void; +declare function f31(x: Partial, y: Partial): void; declare function f40(x: T, y: Readonly): void; -declare function f41(x: T, y: Readonly): void; +declare function f41(x: Readonly, y: Readonly): void; declare type Item = { name: string; }; diff --git a/tests/cases/compiler/mappedTypePartialConstraints.ts b/tests/cases/compiler/mappedTypePartialConstraints.ts new file mode 100644 index 0000000000000..92fa339403424 --- /dev/null +++ b/tests/cases/compiler/mappedTypePartialConstraints.ts @@ -0,0 +1,15 @@ +// Repro from #16985 + +interface MyInterface { + something: number; +} + +class MyClass { + doIt(data : Partial) {} +} + +class MySubClass extends MyClass {} + +function fn(arg: typeof MyClass) {}; + +fn(MySubClass); diff --git a/tests/cases/conformance/types/mapped/mappedTypeRelationships.ts b/tests/cases/conformance/types/mapped/mappedTypeRelationships.ts index 587ec1c9d7e61..7acacd6c1d7d8 100644 --- a/tests/cases/conformance/types/mapped/mappedTypeRelationships.ts +++ b/tests/cases/conformance/types/mapped/mappedTypeRelationships.ts @@ -69,14 +69,16 @@ function f23(x: T, y: Readonly, k: K) { y[k] = x[k]; // Error } +type Thing = { a: string, b: string }; + function f30(x: T, y: Partial) { x = y; // Error y = x; } -function f31(x: T, y: Partial) { - x = y; // Error - y = x; +function f31(x: Partial, y: Partial) { + x = y; + y = x; // Error } function f40(x: T, y: Readonly) { @@ -84,9 +86,9 @@ function f40(x: T, y: Readonly) { y = x; } -function f41(x: T, y: Readonly) { +function f41(x: Readonly, y: Readonly) { x = y; - y = x; + y = x; // Error } type Item = {