From 45a6cb7066b5840bd5e2877c7b1b0df7aa87a274 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 8 Mar 2019 13:57:43 -0800 Subject: [PATCH] Reset toplevel flag when higher priority inference takes place (#30265) --- src/compiler/checker.ts | 1 + ...WhenAppropriatelyContextualized.errors.txt | 53 ++++++++++++ ...ralTypesWhenAppropriatelyContextualized.js | 49 +++++++++++ ...pesWhenAppropriatelyContextualized.symbols | 83 ++++++++++++++++++ ...TypesWhenAppropriatelyContextualized.types | 86 +++++++++++++++++++ ...ralTypesWhenAppropriatelyContextualized.ts | 29 +++++++ 6 files changed, 301 insertions(+) create mode 100644 tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.errors.txt create mode 100644 tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.js create mode 100644 tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.symbols create mode 100644 tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.types create mode 100644 tests/cases/compiler/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 510f03ec41b..c856351c897 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14500,6 +14500,7 @@ namespace ts { if (inference.priority === undefined || priority < inference.priority) { inference.candidates = undefined; inference.contraCandidates = undefined; + inference.topLevel = true; inference.priority = priority; } if (priority === inference.priority) { diff --git a/tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.errors.txt b/tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.errors.txt new file mode 100644 index 00000000000..4304bb486ef --- /dev/null +++ b/tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.errors.txt @@ -0,0 +1,53 @@ +tests/cases/compiler/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts(27,23): error TS2345: Argument of type '{ x?: number[] | undefined; y?: string[] | undefined; }' is not assignable to parameter of type '{ y?: number[] | undefined; }'. + Types of property 'y' are incompatible. + Type 'string[] | undefined' is not assignable to type 'number[] | undefined'. + Type 'string[]' is not assignable to type 'number[]'. + Type 'string' is not assignable to type 'number'. +tests/cases/compiler/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts(28,23): error TS2345: Argument of type '{ x?: number[] | undefined; y?: string[] | undefined; }' is not assignable to parameter of type '{ x?: string[] | undefined; }'. + Types of property 'x' are incompatible. + Type 'number[] | undefined' is not assignable to type 'string[] | undefined'. + Type 'number[]' is not assignable to type 'string[]'. + Type 'number' is not assignable to type 'string'. + + +==== tests/cases/compiler/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts (2 errors) ==== + // Using a homomorphic mapped type over `T` + // Produces a lower-priority inference for `T` than other + // positions, allowing one to override the priority the argument + // order would usually imply + type Lower = { [K in keyof T]: T[K] }; + + export function appendToOptionalArray< + K extends string | number | symbol, + T + >( + object: { [x in K]?: Lower[] }, + key: K, + value: T + ) { + const array = object[key]; + if (array) { + array.push(value); + } else { + object[key] = [value]; + } + } + + // e.g. + const foo: {x?: number[]; y?: string[]; } = {}; + appendToOptionalArray(foo, 'x', 123); // ok + appendToOptionalArray(foo, 'y', 'bar'); // ok + appendToOptionalArray(foo, 'y', 12); // should fail + ~~~ +!!! error TS2345: Argument of type '{ x?: number[] | undefined; y?: string[] | undefined; }' is not assignable to parameter of type '{ y?: number[] | undefined; }'. +!!! error TS2345: Types of property 'y' are incompatible. +!!! error TS2345: Type 'string[] | undefined' is not assignable to type 'number[] | undefined'. +!!! error TS2345: Type 'string[]' is not assignable to type 'number[]'. +!!! error TS2345: Type 'string' is not assignable to type 'number'. + appendToOptionalArray(foo, 'x', "no"); // should fail + ~~~ +!!! error TS2345: Argument of type '{ x?: number[] | undefined; y?: string[] | undefined; }' is not assignable to parameter of type '{ x?: string[] | undefined; }'. +!!! error TS2345: Types of property 'x' are incompatible. +!!! error TS2345: Type 'number[] | undefined' is not assignable to type 'string[] | undefined'. +!!! error TS2345: Type 'number[]' is not assignable to type 'string[]'. +!!! error TS2345: Type 'number' is not assignable to type 'string'. \ No newline at end of file diff --git a/tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.js b/tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.js new file mode 100644 index 00000000000..52c3f8f4380 --- /dev/null +++ b/tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.js @@ -0,0 +1,49 @@ +//// [paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts] +// Using a homomorphic mapped type over `T` +// Produces a lower-priority inference for `T` than other +// positions, allowing one to override the priority the argument +// order would usually imply +type Lower = { [K in keyof T]: T[K] }; + +export function appendToOptionalArray< + K extends string | number | symbol, + T +>( + object: { [x in K]?: Lower[] }, + key: K, + value: T +) { + const array = object[key]; + if (array) { + array.push(value); + } else { + object[key] = [value]; + } +} + +// e.g. +const foo: {x?: number[]; y?: string[]; } = {}; +appendToOptionalArray(foo, 'x', 123); // ok +appendToOptionalArray(foo, 'y', 'bar'); // ok +appendToOptionalArray(foo, 'y', 12); // should fail +appendToOptionalArray(foo, 'x', "no"); // should fail + +//// [paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.js] +"use strict"; +exports.__esModule = true; +function appendToOptionalArray(object, key, value) { + var array = object[key]; + if (array) { + array.push(value); + } + else { + object[key] = [value]; + } +} +exports.appendToOptionalArray = appendToOptionalArray; +// e.g. +var foo = {}; +appendToOptionalArray(foo, 'x', 123); // ok +appendToOptionalArray(foo, 'y', 'bar'); // ok +appendToOptionalArray(foo, 'y', 12); // should fail +appendToOptionalArray(foo, 'x', "no"); // should fail diff --git a/tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.symbols b/tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.symbols new file mode 100644 index 00000000000..4bed81b43e6 --- /dev/null +++ b/tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.symbols @@ -0,0 +1,83 @@ +=== tests/cases/compiler/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts === +// Using a homomorphic mapped type over `T` +// Produces a lower-priority inference for `T` than other +// positions, allowing one to override the priority the argument +// order would usually imply +type Lower = { [K in keyof T]: T[K] }; +>Lower : Symbol(Lower, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 0, 0)) +>T : Symbol(T, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 4, 11)) +>K : Symbol(K, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 4, 19)) +>T : Symbol(T, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 4, 11)) +>T : Symbol(T, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 4, 11)) +>K : Symbol(K, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 4, 19)) + +export function appendToOptionalArray< +>appendToOptionalArray : Symbol(appendToOptionalArray, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 4, 41)) + + K extends string | number | symbol, +>K : Symbol(K, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 6, 38)) + + T +>T : Symbol(T, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 7, 37)) + +>( + object: { [x in K]?: Lower[] }, +>object : Symbol(object, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 9, 2)) +>x : Symbol(x, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 10, 13)) +>K : Symbol(K, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 6, 38)) +>Lower : Symbol(Lower, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 0, 0)) +>T : Symbol(T, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 7, 37)) + + key: K, +>key : Symbol(key, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 10, 36)) +>K : Symbol(K, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 6, 38)) + + value: T +>value : Symbol(value, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 11, 9)) +>T : Symbol(T, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 7, 37)) + +) { + const array = object[key]; +>array : Symbol(array, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 14, 7)) +>object : Symbol(object, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 9, 2)) +>key : Symbol(key, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 10, 36)) + + if (array) { +>array : Symbol(array, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 14, 7)) + + array.push(value); +>array.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>array : Symbol(array, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 14, 7)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>value : Symbol(value, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 11, 9)) + + } else { + object[key] = [value]; +>object : Symbol(object, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 9, 2)) +>key : Symbol(key, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 10, 36)) +>value : Symbol(value, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 11, 9)) + } +} + +// e.g. +const foo: {x?: number[]; y?: string[]; } = {}; +>foo : Symbol(foo, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 23, 5)) +>x : Symbol(x, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 23, 12)) +>y : Symbol(y, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 23, 25)) + +appendToOptionalArray(foo, 'x', 123); // ok +>appendToOptionalArray : Symbol(appendToOptionalArray, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 4, 41)) +>foo : Symbol(foo, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 23, 5)) + +appendToOptionalArray(foo, 'y', 'bar'); // ok +>appendToOptionalArray : Symbol(appendToOptionalArray, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 4, 41)) +>foo : Symbol(foo, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 23, 5)) + +appendToOptionalArray(foo, 'y', 12); // should fail +>appendToOptionalArray : Symbol(appendToOptionalArray, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 4, 41)) +>foo : Symbol(foo, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 23, 5)) + +appendToOptionalArray(foo, 'x', "no"); // should fail +>appendToOptionalArray : Symbol(appendToOptionalArray, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 4, 41)) +>foo : Symbol(foo, Decl(paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts, 23, 5)) + diff --git a/tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.types b/tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.types new file mode 100644 index 00000000000..a6554284225 --- /dev/null +++ b/tests/baselines/reference/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.types @@ -0,0 +1,86 @@ +=== tests/cases/compiler/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts === +// Using a homomorphic mapped type over `T` +// Produces a lower-priority inference for `T` than other +// positions, allowing one to override the priority the argument +// order would usually imply +type Lower = { [K in keyof T]: T[K] }; +>Lower : Lower + +export function appendToOptionalArray< +>appendToOptionalArray : (object: { [x in K]?: Lower[] | undefined; }, key: K, value: T) => void + + K extends string | number | symbol, + T +>( + object: { [x in K]?: Lower[] }, +>object : { [x in K]?: Lower[] | undefined; } + + key: K, +>key : K + + value: T +>value : T + +) { + const array = object[key]; +>array : { [x in K]?: Lower[] | undefined; }[K] +>object[key] : { [x in K]?: Lower[] | undefined; }[K] +>object : { [x in K]?: Lower[] | undefined; } +>key : K + + if (array) { +>array : { [x in K]?: Lower[] | undefined; }[K] + + array.push(value); +>array.push(value) : number +>array.push : (...items: Lower[]) => number +>array : Lower[] +>push : (...items: Lower[]) => number +>value : T + + } else { + object[key] = [value]; +>object[key] = [value] : T[] +>object[key] : { [x in K]?: Lower[] | undefined; }[K] +>object : { [x in K]?: Lower[] | undefined; } +>key : K +>[value] : T[] +>value : T + } +} + +// e.g. +const foo: {x?: number[]; y?: string[]; } = {}; +>foo : { x?: number[] | undefined; y?: string[] | undefined; } +>x : number[] | undefined +>y : string[] | undefined +>{} : {} + +appendToOptionalArray(foo, 'x', 123); // ok +>appendToOptionalArray(foo, 'x', 123) : void +>appendToOptionalArray : (object: { [x in K]?: Lower[] | undefined; }, key: K, value: T) => void +>foo : { x?: number[] | undefined; y?: string[] | undefined; } +>'x' : "x" +>123 : 123 + +appendToOptionalArray(foo, 'y', 'bar'); // ok +>appendToOptionalArray(foo, 'y', 'bar') : void +>appendToOptionalArray : (object: { [x in K]?: Lower[] | undefined; }, key: K, value: T) => void +>foo : { x?: number[] | undefined; y?: string[] | undefined; } +>'y' : "y" +>'bar' : "bar" + +appendToOptionalArray(foo, 'y', 12); // should fail +>appendToOptionalArray(foo, 'y', 12) : any +>appendToOptionalArray : (object: { [x in K]?: Lower[] | undefined; }, key: K, value: T) => void +>foo : { x?: number[] | undefined; y?: string[] | undefined; } +>'y' : "y" +>12 : 12 + +appendToOptionalArray(foo, 'x', "no"); // should fail +>appendToOptionalArray(foo, 'x', "no") : any +>appendToOptionalArray : (object: { [x in K]?: Lower[] | undefined; }, key: K, value: T) => void +>foo : { x?: number[] | undefined; y?: string[] | undefined; } +>'x' : "x" +>"no" : "no" + diff --git a/tests/cases/compiler/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts b/tests/cases/compiler/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts new file mode 100644 index 00000000000..280cf31e908 --- /dev/null +++ b/tests/cases/compiler/paramsOnlyHaveLiteralTypesWhenAppropriatelyContextualized.ts @@ -0,0 +1,29 @@ +// @strict: true +// Using a homomorphic mapped type over `T` +// Produces a lower-priority inference for `T` than other +// positions, allowing one to override the priority the argument +// order would usually imply +type Lower = { [K in keyof T]: T[K] }; + +export function appendToOptionalArray< + K extends string | number | symbol, + T +>( + object: { [x in K]?: Lower[] }, + key: K, + value: T +) { + const array = object[key]; + if (array) { + array.push(value); + } else { + object[key] = [value]; + } +} + +// e.g. +const foo: {x?: number[]; y?: string[]; } = {}; +appendToOptionalArray(foo, 'x', 123); // ok +appendToOptionalArray(foo, 'y', 'bar'); // ok +appendToOptionalArray(foo, 'y', 12); // should fail +appendToOptionalArray(foo, 'x', "no"); // should fail \ No newline at end of file