From a885894baabf6187cf24c8c16732c7bfa1156985 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 18 Oct 2017 16:58:56 -0700 Subject: [PATCH 1/3] Infer properties of type {} when argument of a mapped type is incorrect --- src/compiler/checker.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 219f370fd53..7b2455f2553 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10667,29 +10667,27 @@ namespace ts { const optionalMask = target.declaration.questionToken ? 0 : SymbolFlags.Optional; const members = createSymbolTable(); for (const prop of properties) { - const inferredPropType = inferTargetType(getTypeOfSymbol(prop)); - if (!inferredPropType) { + const propType = getTypeOfSymbol(prop); + // If any property contains context sensitive functions that have been skipped, the source type + // is incomplete and we can't infer a meaningful input type. + if (propType.flags & TypeFlags.ContainsAnyFunctionType) { return undefined; } const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName); inferredProp.checkFlags = readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0; inferredProp.declarations = prop.declarations; - inferredProp.type = inferredPropType; + inferredProp.type = inferTargetType(propType); members.set(prop.escapedName, inferredProp); } if (indexInfo) { - const inferredIndexType = inferTargetType(indexInfo.type); - if (!inferredIndexType) { - return undefined; - } - indexInfo = createIndexInfo(inferredIndexType, readonlyMask && indexInfo.isReadonly); + indexInfo = createIndexInfo(inferTargetType(indexInfo.type), readonlyMask && indexInfo.isReadonly); } return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined); function inferTargetType(sourceType: Type): Type { inference.candidates = undefined; inferTypes(inferences, sourceType, templateType); - return inference.candidates && getUnionType(inference.candidates, /*subtypeReduction*/ true); + return inference.candidates ? getUnionType(inference.candidates, /*subtypeReduction*/ true) : emptyObjectType; } } From e0c10853ce84e95dccd93fe643e27667aa800e9c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 18 Oct 2017 16:59:35 -0700 Subject: [PATCH 2/3] Accept new baselines --- tests/baselines/reference/keyofAndIndexedAccess.js | 4 +++- tests/baselines/reference/keyofAndIndexedAccess.types | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/baselines/reference/keyofAndIndexedAccess.js b/tests/baselines/reference/keyofAndIndexedAccess.js index 1a68d9d484d..530a2b8bbf3 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.js +++ b/tests/baselines/reference/keyofAndIndexedAccess.js @@ -1101,7 +1101,9 @@ declare type Handlers = { [K in keyof T]: (t: T[K]) => void; }; declare function on(handlerHash: Handlers): T; -declare var hashOfEmpty1: {}; +declare var hashOfEmpty1: { + test: {}; +}; declare var hashOfEmpty2: { test: boolean; }; diff --git a/tests/baselines/reference/keyofAndIndexedAccess.types b/tests/baselines/reference/keyofAndIndexedAccess.types index 4bd5bb4e3c4..9ae7b793ce2 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.types +++ b/tests/baselines/reference/keyofAndIndexedAccess.types @@ -1793,8 +1793,8 @@ declare function on(handlerHash: Handlers): T >T : T var hashOfEmpty1 = on({ test: () => {} }); // {} ->hashOfEmpty1 : {} ->on({ test: () => {} }) : {} +>hashOfEmpty1 : { test: {}; } +>on({ test: () => {} }) : { test: {}; } >on : (handlerHash: Handlers) => T >{ test: () => {} } : { test: () => void; } >test : () => void From f8d9079c478cb6f17d985fd6eca48ceb06c0b3e4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 18 Oct 2017 16:59:43 -0700 Subject: [PATCH 3/3] Add regression test --- .../mappedTypeInferenceErrors.errors.txt | 44 +++++++++++++ .../reference/mappedTypeInferenceErrors.js | 34 ++++++++++ .../mappedTypeInferenceErrors.symbols | 55 ++++++++++++++++ .../reference/mappedTypeInferenceErrors.types | 64 +++++++++++++++++++ .../types/mapped/mappedTypeInferenceErrors.ts | 20 ++++++ 5 files changed, 217 insertions(+) create mode 100644 tests/baselines/reference/mappedTypeInferenceErrors.errors.txt create mode 100644 tests/baselines/reference/mappedTypeInferenceErrors.js create mode 100644 tests/baselines/reference/mappedTypeInferenceErrors.symbols create mode 100644 tests/baselines/reference/mappedTypeInferenceErrors.types create mode 100644 tests/cases/conformance/types/mapped/mappedTypeInferenceErrors.ts diff --git a/tests/baselines/reference/mappedTypeInferenceErrors.errors.txt b/tests/baselines/reference/mappedTypeInferenceErrors.errors.txt new file mode 100644 index 00000000000..28bc188f7b6 --- /dev/null +++ b/tests/baselines/reference/mappedTypeInferenceErrors.errors.txt @@ -0,0 +1,44 @@ +tests/cases/conformance/types/mapped/mappedTypeInferenceErrors.ts(9,5): error TS2345: Argument of type '{ props: { x: number; y: number; }; computed: { bar(): number; baz: number; }; }' is not assignable to parameter of type '{ props: { x: number; y: number; }; computed: ComputedOf<{ bar: number; baz: {}; }>; } & ThisType<{ x: number; y: number; } & { bar: number; baz: {}; }>'. + Type '{ props: { x: number; y: number; }; computed: { bar(): number; baz: number; }; }' is not assignable to type '{ props: { x: number; y: number; }; computed: ComputedOf<{ bar: number; baz: {}; }>; }'. + Types of property 'computed' are incompatible. + Type '{ bar(): number; baz: number; }' is not assignable to type 'ComputedOf<{ bar: number; baz: {}; }>'. + Types of property 'baz' are incompatible. + Type 'number' is not assignable to type '() => {}'. + + +==== tests/cases/conformance/types/mapped/mappedTypeInferenceErrors.ts (1 errors) ==== + // Repro from #19316 + + type ComputedOf = { + [K in keyof T]: () => T[K]; + } + + declare function foo(options: { props: P, computed: ComputedOf } & ThisType

): void; + + foo({ + ~ + props: { x: 10, y: 20 }, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + computed: { + ~~~~~~~~~~~~~~~ + bar(): number { + ~~~~~~~~~~~~~~~~~~~~~~~ + let z = this.bar; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + return 42; + ~~~~~~~~~~~~~~~~~~~~~~ + }, + ~~~~~~~~~~ + baz: 42 + ~~~~~~~~~~~~~~~ + } + ~~~~~ + }); + ~ +!!! error TS2345: Argument of type '{ props: { x: number; y: number; }; computed: { bar(): number; baz: number; }; }' is not assignable to parameter of type '{ props: { x: number; y: number; }; computed: ComputedOf<{ bar: number; baz: {}; }>; } & ThisType<{ x: number; y: number; } & { bar: number; baz: {}; }>'. +!!! error TS2345: Type '{ props: { x: number; y: number; }; computed: { bar(): number; baz: number; }; }' is not assignable to type '{ props: { x: number; y: number; }; computed: ComputedOf<{ bar: number; baz: {}; }>; }'. +!!! error TS2345: Types of property 'computed' are incompatible. +!!! error TS2345: Type '{ bar(): number; baz: number; }' is not assignable to type 'ComputedOf<{ bar: number; baz: {}; }>'. +!!! error TS2345: Types of property 'baz' are incompatible. +!!! error TS2345: Type 'number' is not assignable to type '() => {}'. + \ No newline at end of file diff --git a/tests/baselines/reference/mappedTypeInferenceErrors.js b/tests/baselines/reference/mappedTypeInferenceErrors.js new file mode 100644 index 00000000000..5260bacb792 --- /dev/null +++ b/tests/baselines/reference/mappedTypeInferenceErrors.js @@ -0,0 +1,34 @@ +//// [mappedTypeInferenceErrors.ts] +// Repro from #19316 + +type ComputedOf = { + [K in keyof T]: () => T[K]; +} + +declare function foo(options: { props: P, computed: ComputedOf } & ThisType

): void; + +foo({ + props: { x: 10, y: 20 }, + computed: { + bar(): number { + let z = this.bar; + return 42; + }, + baz: 42 + } +}); + + +//// [mappedTypeInferenceErrors.js] +"use strict"; +// Repro from #19316 +foo({ + props: { x: 10, y: 20 }, + computed: { + bar: function () { + var z = this.bar; + return 42; + }, + baz: 42 + } +}); diff --git a/tests/baselines/reference/mappedTypeInferenceErrors.symbols b/tests/baselines/reference/mappedTypeInferenceErrors.symbols new file mode 100644 index 00000000000..f8656a42cb1 --- /dev/null +++ b/tests/baselines/reference/mappedTypeInferenceErrors.symbols @@ -0,0 +1,55 @@ +=== tests/cases/conformance/types/mapped/mappedTypeInferenceErrors.ts === +// Repro from #19316 + +type ComputedOf = { +>ComputedOf : Symbol(ComputedOf, Decl(mappedTypeInferenceErrors.ts, 0, 0)) +>T : Symbol(T, Decl(mappedTypeInferenceErrors.ts, 2, 16)) + + [K in keyof T]: () => T[K]; +>K : Symbol(K, Decl(mappedTypeInferenceErrors.ts, 3, 5)) +>T : Symbol(T, Decl(mappedTypeInferenceErrors.ts, 2, 16)) +>T : Symbol(T, Decl(mappedTypeInferenceErrors.ts, 2, 16)) +>K : Symbol(K, Decl(mappedTypeInferenceErrors.ts, 3, 5)) +} + +declare function foo(options: { props: P, computed: ComputedOf } & ThisType

): void; +>foo : Symbol(foo, Decl(mappedTypeInferenceErrors.ts, 4, 1)) +>P : Symbol(P, Decl(mappedTypeInferenceErrors.ts, 6, 21)) +>C : Symbol(C, Decl(mappedTypeInferenceErrors.ts, 6, 23)) +>options : Symbol(options, Decl(mappedTypeInferenceErrors.ts, 6, 27)) +>props : Symbol(props, Decl(mappedTypeInferenceErrors.ts, 6, 37)) +>P : Symbol(P, Decl(mappedTypeInferenceErrors.ts, 6, 21)) +>computed : Symbol(computed, Decl(mappedTypeInferenceErrors.ts, 6, 47)) +>ComputedOf : Symbol(ComputedOf, Decl(mappedTypeInferenceErrors.ts, 0, 0)) +>C : Symbol(C, Decl(mappedTypeInferenceErrors.ts, 6, 23)) +>ThisType : Symbol(ThisType, Decl(lib.d.ts, --, --)) +>P : Symbol(P, Decl(mappedTypeInferenceErrors.ts, 6, 21)) +>C : Symbol(C, Decl(mappedTypeInferenceErrors.ts, 6, 23)) + +foo({ +>foo : Symbol(foo, Decl(mappedTypeInferenceErrors.ts, 4, 1)) + + props: { x: 10, y: 20 }, +>props : Symbol(props, Decl(mappedTypeInferenceErrors.ts, 8, 5)) +>x : Symbol(x, Decl(mappedTypeInferenceErrors.ts, 9, 12)) +>y : Symbol(y, Decl(mappedTypeInferenceErrors.ts, 9, 19)) + + computed: { +>computed : Symbol(computed, Decl(mappedTypeInferenceErrors.ts, 9, 28)) + + bar(): number { +>bar : Symbol(bar, Decl(mappedTypeInferenceErrors.ts, 10, 15)) + + let z = this.bar; +>z : Symbol(z, Decl(mappedTypeInferenceErrors.ts, 12, 15)) +>this.bar : Symbol(bar, Decl(mappedTypeInferenceErrors.ts, 10, 15)) +>this : Symbol(__object, Decl(mappedTypeInferenceErrors.ts, 10, 13)) +>bar : Symbol(bar, Decl(mappedTypeInferenceErrors.ts, 10, 15)) + + return 42; + }, + baz: 42 +>baz : Symbol(baz, Decl(mappedTypeInferenceErrors.ts, 14, 10)) + } +}); + diff --git a/tests/baselines/reference/mappedTypeInferenceErrors.types b/tests/baselines/reference/mappedTypeInferenceErrors.types new file mode 100644 index 00000000000..d9cfcd339fa --- /dev/null +++ b/tests/baselines/reference/mappedTypeInferenceErrors.types @@ -0,0 +1,64 @@ +=== tests/cases/conformance/types/mapped/mappedTypeInferenceErrors.ts === +// Repro from #19316 + +type ComputedOf = { +>ComputedOf : ComputedOf +>T : T + + [K in keyof T]: () => T[K]; +>K : K +>T : T +>T : T +>K : K +} + +declare function foo(options: { props: P, computed: ComputedOf } & ThisType

): void; +>foo : (options: { props: P; computed: ComputedOf; } & ThisType

) => void +>P : P +>C : C +>options : { props: P; computed: ComputedOf; } & ThisType

+>props : P +>P : P +>computed : ComputedOf +>ComputedOf : ComputedOf +>C : C +>ThisType : ThisType +>P : P +>C : C + +foo({ +>foo({ props: { x: 10, y: 20 }, computed: { bar(): number { let z = this.bar; return 42; }, baz: 42 }}) : any +>foo : (options: { props: P; computed: ComputedOf; } & ThisType

) => void +>{ props: { x: 10, y: 20 }, computed: { bar(): number { let z = this.bar; return 42; }, baz: 42 }} : { props: { x: number; y: number; }; computed: { bar(): number; baz: number; }; } + + props: { x: 10, y: 20 }, +>props : { x: number; y: number; } +>{ x: 10, y: 20 } : { x: number; y: number; } +>x : number +>10 : 10 +>y : number +>20 : 20 + + computed: { +>computed : { bar(): number; baz: number; } +>{ bar(): number { let z = this.bar; return 42; }, baz: 42 } : { bar(): number; baz: number; } + + bar(): number { +>bar : () => number + + let z = this.bar; +>z : () => number +>this.bar : () => number +>this : { bar(): number; baz: number; } +>bar : () => number + + return 42; +>42 : 42 + + }, + baz: 42 +>baz : number +>42 : 42 + } +}); + diff --git a/tests/cases/conformance/types/mapped/mappedTypeInferenceErrors.ts b/tests/cases/conformance/types/mapped/mappedTypeInferenceErrors.ts new file mode 100644 index 00000000000..9ec7aca15ba --- /dev/null +++ b/tests/cases/conformance/types/mapped/mappedTypeInferenceErrors.ts @@ -0,0 +1,20 @@ +// @strict: true + +// Repro from #19316 + +type ComputedOf = { + [K in keyof T]: () => T[K]; +} + +declare function foo(options: { props: P, computed: ComputedOf } & ThisType

): void; + +foo({ + props: { x: 10, y: 20 }, + computed: { + bar(): number { + let z = this.bar; + return 42; + }, + baz: 42 + } +});