Do not consider binding patterns in contextual types for return type inference where all the signature type parameters have defaults (#39081)

This commit is contained in:
Wesley Wigham 2020-06-24 13:45:17 -07:00 committed by GitHub
parent 785e44704c
commit d1ebf126d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 271 additions and 8 deletions

View File

@ -22855,14 +22855,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 = <VariableLikeDeclaration>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);
}
}
@ -22889,8 +22889,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)]);
@ -23355,14 +23355,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(<YieldExpression>parent);
case SyntaxKind.AwaitExpression:
return getContextualTypeForAwaitOperand(<AwaitExpression>parent);
return getContextualTypeForAwaitOperand(<AwaitExpression>parent, contextFlags);
case SyntaxKind.CallExpression:
if ((<CallExpression>parent).expression.kind === SyntaxKind.ImportKeyword) {
return stringType;
@ -25700,7 +25700,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

View File

@ -4136,7 +4136,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!

View File

@ -0,0 +1,97 @@
//// [destructureOfVariableSameAsShorthand.ts]
// https://github.com/microsoft/TypeScript/issues/38969
interface AxiosResponse<T = never> {
data: T;
}
declare function get<T = never, R = AxiosResponse<T>>(): Promise<R>;
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<never>();
// 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*/];
}
});
});
}

View File

@ -0,0 +1,69 @@
=== tests/cases/compiler/destructureOfVariableSameAsShorthand.ts ===
// https://github.com/microsoft/TypeScript/issues/38969
interface AxiosResponse<T = never> {
>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<T = never, R = AxiosResponse<T>>(): Promise<R>;
>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<never>();
>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))
}

View File

@ -0,0 +1,71 @@
=== tests/cases/compiler/destructureOfVariableSameAsShorthand.ts ===
// https://github.com/microsoft/TypeScript/issues/38969
interface AxiosResponse<T = never> {
data: T;
>data : T
}
declare function get<T = never, R = AxiosResponse<T>>(): Promise<R>;
>get : <T = never, R = AxiosResponse<T>>() => Promise<R>
async function main() {
>main : () => Promise<void>
// These work examples as expected
get().then((response) => {
>get().then((response) => { // body is never const body = response.data; }) : Promise<void>
>get().then : <TResult1 = AxiosResponse<never>, TResult2 = never>(onfulfilled?: (value: AxiosResponse<never>) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
>get() : Promise<AxiosResponse<never>>
>get : <T = never, R = AxiosResponse<T>>() => Promise<R>
>then : <TResult1 = AxiosResponse<never>, TResult2 = never>(onfulfilled?: (value: AxiosResponse<never>) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
>(response) => { // body is never const body = response.data; } : (response: AxiosResponse<never>) => void
>response : AxiosResponse<never>
// body is never
const body = response.data;
>body : never
>response.data : never
>response : AxiosResponse<never>
>data : never
})
get().then(({ data }) => {
>get().then(({ data }) => { // data is never }) : Promise<void>
>get().then : <TResult1 = AxiosResponse<never>, TResult2 = never>(onfulfilled?: (value: AxiosResponse<never>) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
>get() : Promise<AxiosResponse<never>>
>get : <T = never, R = AxiosResponse<T>>() => Promise<R>
>then : <TResult1 = AxiosResponse<never>, TResult2 = never>(onfulfilled?: (value: AxiosResponse<never>) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
>({ data }) => { // data is never } : ({ data }: AxiosResponse<never>) => void
>data : never
// data is never
})
const response = await get()
>response : AxiosResponse<never>
>await get() : AxiosResponse<never>
>get() : Promise<AxiosResponse<never>>
>get : <T = never, R = AxiosResponse<T>>() => Promise<R>
// body is never
const body = response.data;
>body : never
>response.data : never
>response : AxiosResponse<never>
>data : never
// data is never
const { data } = await get<never>();
>data : never
>await get<never>() : AxiosResponse<never>
>get<never>() : Promise<AxiosResponse<never>>
>get : <T = never, R = AxiosResponse<T>>() => Promise<R>
// 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<never>
>get() : Promise<AxiosResponse<never>>
>get : <T = never, R = AxiosResponse<T>>() => Promise<R>
}

View File

@ -0,0 +1,26 @@
// https://github.com/microsoft/TypeScript/issues/38969
interface AxiosResponse<T = never> {
data: T;
}
declare function get<T = never, R = AxiosResponse<T>>(): Promise<R>;
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<never>();
// The following did not work as expected.
// shouldBeNever should be never, but was any
const { data: shouldBeNever } = await get();
}