From a42e1ae3b8ae4eae8de8e7d7e294c430dedb782f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 15 Jun 2020 13:03:01 -0700 Subject: [PATCH] Do not consider binding patterns in contextual types for return type inference where all the signature type parameters have defaults --- src/compiler/checker.ts | 14 +-- src/compiler/types.ts | 2 +- .../destructureOfVariableSameAsShorthand.js | 97 +++++++++++++++++++ ...structureOfVariableSameAsShorthand.symbols | 69 +++++++++++++ ...destructureOfVariableSameAsShorthand.types | 71 ++++++++++++++ .../destructureOfVariableSameAsShorthand.ts | 26 +++++ 6 files changed, 271 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/destructureOfVariableSameAsShorthand.js create mode 100644 tests/baselines/reference/destructureOfVariableSameAsShorthand.symbols create mode 100644 tests/baselines/reference/destructureOfVariableSameAsShorthand.types create mode 100644 tests/cases/compiler/destructureOfVariableSameAsShorthand.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fe4b3433a9909..c873905c9c81f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22416,14 +22416,14 @@ namespace ts { // the contextual type of an initializer expression is the type implied by the binding pattern. // Otherwise, in a binding pattern inside a variable or parameter declaration, // the contextual type of an initializer expression is the type annotation of the containing declaration, if present. - function getContextualTypeForInitializerExpression(node: Expression): Type | undefined { + function getContextualTypeForInitializerExpression(node: Expression, contextFlags?: ContextFlags): Type | undefined { const declaration = node.parent; if (hasInitializer(declaration) && node === declaration.initializer) { const result = getContextualTypeForVariableLikeDeclaration(declaration); if (result) { return result; } - if (isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable + if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false); } } @@ -22450,8 +22450,8 @@ namespace ts { return undefined; } - function getContextualTypeForAwaitOperand(node: AwaitExpression): Type | undefined { - const contextualType = getContextualType(node); + function getContextualTypeForAwaitOperand(node: AwaitExpression, contextFlags?: ContextFlags): Type | undefined { + const contextualType = getContextualType(node, contextFlags); if (contextualType) { const contextualAwaitedType = getAwaitedType(contextualType); return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); @@ -22916,14 +22916,14 @@ namespace ts { case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.BindingElement: - return getContextualTypeForInitializerExpression(node); + return getContextualTypeForInitializerExpression(node, contextFlags); case SyntaxKind.ArrowFunction: case SyntaxKind.ReturnStatement: return getContextualTypeForReturnExpression(node); case SyntaxKind.YieldExpression: return getContextualTypeForYieldOperand(parent); case SyntaxKind.AwaitExpression: - return getContextualTypeForAwaitOperand(parent); + return getContextualTypeForAwaitOperand(parent, contextFlags); case SyntaxKind.CallExpression: if ((parent).expression.kind === SyntaxKind.ImportKeyword) { return stringType; @@ -25271,7 +25271,7 @@ namespace ts { // 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the // return type of 'wrap'. if (node.kind !== SyntaxKind.Decorator) { - const contextualType = getContextualType(node); + const contextualType = getContextualType(node, every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)) ? ContextFlags.SkipBindingPatterns : ContextFlags.None); if (contextualType) { // We clone the inference context to avoid disturbing a resolution in progress for an // outer call expression. Effectively we just want a snapshot of whatever has been diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9d4f2fd9077a2..de9a0a4905110 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3676,7 +3676,7 @@ namespace ts { Signature = 1 << 0, // Obtaining contextual signature NoConstraints = 1 << 1, // Don't obtain type variable constraints Completions = 1 << 2, // Ignore inference to current node and parent nodes out to the containing call for completions - + SkipBindingPatterns = 1 << 3, // Ignore contextual types applied by binding patterns } // NOTE: If modifying this enum, must modify `TypeFormatFlags` too! diff --git a/tests/baselines/reference/destructureOfVariableSameAsShorthand.js b/tests/baselines/reference/destructureOfVariableSameAsShorthand.js new file mode 100644 index 0000000000000..2adf58de84bf0 --- /dev/null +++ b/tests/baselines/reference/destructureOfVariableSameAsShorthand.js @@ -0,0 +1,97 @@ +//// [destructureOfVariableSameAsShorthand.ts] +// https://github.com/microsoft/TypeScript/issues/38969 +interface AxiosResponse { + data: T; +} + +declare function get>(): Promise; + +async function main() { + // These work examples as expected + get().then((response) => { + // body is never + const body = response.data; + }) + get().then(({ data }) => { + // data is never + }) + const response = await get() + // body is never + const body = response.data; + // data is never + const { data } = await get(); + + // The following did not work as expected. + // shouldBeNever should be never, but was any + const { data: shouldBeNever } = await get(); +} + +//// [destructureOfVariableSameAsShorthand.js] +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +function main() { + return __awaiter(this, void 0, void 0, function () { + var response, body, data, shouldBeNever; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + // These work examples as expected + get().then(function (response) { + // body is never + var body = response.data; + }); + get().then(function (_a) { + var data = _a.data; + // data is never + }); + return [4 /*yield*/, get() + // body is never + ]; + case 1: + response = _a.sent(); + body = response.data; + return [4 /*yield*/, get()]; + case 2: + data = (_a.sent()).data; + return [4 /*yield*/, get()]; + case 3: + shouldBeNever = (_a.sent()).data; + return [2 /*return*/]; + } + }); + }); +} diff --git a/tests/baselines/reference/destructureOfVariableSameAsShorthand.symbols b/tests/baselines/reference/destructureOfVariableSameAsShorthand.symbols new file mode 100644 index 0000000000000..470a85e199a35 --- /dev/null +++ b/tests/baselines/reference/destructureOfVariableSameAsShorthand.symbols @@ -0,0 +1,69 @@ +=== tests/cases/compiler/destructureOfVariableSameAsShorthand.ts === +// https://github.com/microsoft/TypeScript/issues/38969 +interface AxiosResponse { +>AxiosResponse : Symbol(AxiosResponse, Decl(destructureOfVariableSameAsShorthand.ts, 0, 0)) +>T : Symbol(T, Decl(destructureOfVariableSameAsShorthand.ts, 1, 24)) + + data: T; +>data : Symbol(AxiosResponse.data, Decl(destructureOfVariableSameAsShorthand.ts, 1, 36)) +>T : Symbol(T, Decl(destructureOfVariableSameAsShorthand.ts, 1, 24)) +} + +declare function get>(): Promise; +>get : Symbol(get, Decl(destructureOfVariableSameAsShorthand.ts, 3, 1)) +>T : Symbol(T, Decl(destructureOfVariableSameAsShorthand.ts, 5, 21)) +>R : Symbol(R, Decl(destructureOfVariableSameAsShorthand.ts, 5, 31)) +>AxiosResponse : Symbol(AxiosResponse, Decl(destructureOfVariableSameAsShorthand.ts, 0, 0)) +>T : Symbol(T, Decl(destructureOfVariableSameAsShorthand.ts, 5, 21)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --)) +>R : Symbol(R, Decl(destructureOfVariableSameAsShorthand.ts, 5, 31)) + +async function main() { +>main : Symbol(main, Decl(destructureOfVariableSameAsShorthand.ts, 5, 68)) + + // These work examples as expected + get().then((response) => { +>get().then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>get : Symbol(get, Decl(destructureOfVariableSameAsShorthand.ts, 3, 1)) +>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>response : Symbol(response, Decl(destructureOfVariableSameAsShorthand.ts, 9, 16)) + + // body is never + const body = response.data; +>body : Symbol(body, Decl(destructureOfVariableSameAsShorthand.ts, 11, 13)) +>response.data : Symbol(AxiosResponse.data, Decl(destructureOfVariableSameAsShorthand.ts, 1, 36)) +>response : Symbol(response, Decl(destructureOfVariableSameAsShorthand.ts, 9, 16)) +>data : Symbol(AxiosResponse.data, Decl(destructureOfVariableSameAsShorthand.ts, 1, 36)) + + }) + get().then(({ data }) => { +>get().then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>get : Symbol(get, Decl(destructureOfVariableSameAsShorthand.ts, 3, 1)) +>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --)) +>data : Symbol(data, Decl(destructureOfVariableSameAsShorthand.ts, 13, 17)) + + // data is never + }) + const response = await get() +>response : Symbol(response, Decl(destructureOfVariableSameAsShorthand.ts, 16, 9)) +>get : Symbol(get, Decl(destructureOfVariableSameAsShorthand.ts, 3, 1)) + + // body is never + const body = response.data; +>body : Symbol(body, Decl(destructureOfVariableSameAsShorthand.ts, 18, 9)) +>response.data : Symbol(AxiosResponse.data, Decl(destructureOfVariableSameAsShorthand.ts, 1, 36)) +>response : Symbol(response, Decl(destructureOfVariableSameAsShorthand.ts, 16, 9)) +>data : Symbol(AxiosResponse.data, Decl(destructureOfVariableSameAsShorthand.ts, 1, 36)) + + // data is never + const { data } = await get(); +>data : Symbol(data, Decl(destructureOfVariableSameAsShorthand.ts, 20, 11)) +>get : Symbol(get, Decl(destructureOfVariableSameAsShorthand.ts, 3, 1)) + + // The following did not work as expected. + // shouldBeNever should be never, but was any + const { data: shouldBeNever } = await get(); +>data : Symbol(AxiosResponse.data, Decl(destructureOfVariableSameAsShorthand.ts, 1, 36)) +>shouldBeNever : Symbol(shouldBeNever, Decl(destructureOfVariableSameAsShorthand.ts, 24, 11)) +>get : Symbol(get, Decl(destructureOfVariableSameAsShorthand.ts, 3, 1)) +} diff --git a/tests/baselines/reference/destructureOfVariableSameAsShorthand.types b/tests/baselines/reference/destructureOfVariableSameAsShorthand.types new file mode 100644 index 0000000000000..fab3c33831b4d --- /dev/null +++ b/tests/baselines/reference/destructureOfVariableSameAsShorthand.types @@ -0,0 +1,71 @@ +=== tests/cases/compiler/destructureOfVariableSameAsShorthand.ts === +// https://github.com/microsoft/TypeScript/issues/38969 +interface AxiosResponse { + data: T; +>data : T +} + +declare function get>(): Promise; +>get : >() => Promise + +async function main() { +>main : () => Promise + + // These work examples as expected + get().then((response) => { +>get().then((response) => { // body is never const body = response.data; }) : Promise +>get().then : , TResult2 = never>(onfulfilled?: (value: AxiosResponse) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>get() : Promise> +>get : >() => Promise +>then : , TResult2 = never>(onfulfilled?: (value: AxiosResponse) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>(response) => { // body is never const body = response.data; } : (response: AxiosResponse) => void +>response : AxiosResponse + + // body is never + const body = response.data; +>body : never +>response.data : never +>response : AxiosResponse +>data : never + + }) + get().then(({ data }) => { +>get().then(({ data }) => { // data is never }) : Promise +>get().then : , TResult2 = never>(onfulfilled?: (value: AxiosResponse) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>get() : Promise> +>get : >() => Promise +>then : , TResult2 = never>(onfulfilled?: (value: AxiosResponse) => TResult1 | PromiseLike, onrejected?: (reason: any) => TResult2 | PromiseLike) => Promise +>({ data }) => { // data is never } : ({ data }: AxiosResponse) => void +>data : never + + // data is never + }) + const response = await get() +>response : AxiosResponse +>await get() : AxiosResponse +>get() : Promise> +>get : >() => Promise + + // body is never + const body = response.data; +>body : never +>response.data : never +>response : AxiosResponse +>data : never + + // data is never + const { data } = await get(); +>data : never +>await get() : AxiosResponse +>get() : Promise> +>get : >() => Promise + + // The following did not work as expected. + // shouldBeNever should be never, but was any + const { data: shouldBeNever } = await get(); +>data : any +>shouldBeNever : never +>await get() : AxiosResponse +>get() : Promise> +>get : >() => Promise +} diff --git a/tests/cases/compiler/destructureOfVariableSameAsShorthand.ts b/tests/cases/compiler/destructureOfVariableSameAsShorthand.ts new file mode 100644 index 0000000000000..0feb383185a38 --- /dev/null +++ b/tests/cases/compiler/destructureOfVariableSameAsShorthand.ts @@ -0,0 +1,26 @@ +// https://github.com/microsoft/TypeScript/issues/38969 +interface AxiosResponse { + data: T; +} + +declare function get>(): Promise; + +async function main() { + // These work examples as expected + get().then((response) => { + // body is never + const body = response.data; + }) + get().then(({ data }) => { + // data is never + }) + const response = await get() + // body is never + const body = response.data; + // data is never + const { data } = await get(); + + // The following did not work as expected. + // shouldBeNever should be never, but was any + const { data: shouldBeNever } = await get(); +} \ No newline at end of file