Skip to content

Do not consider binding patterns in contextual types for return type … #39081

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <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);
}
}
Expand All @@ -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)]);
Expand Down Expand Up @@ -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(<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;
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
97 changes: 97 additions & 0 deletions tests/baselines/reference/destructureOfVariableSameAsShorthand.js
Original file line number Diff line number Diff line change
@@ -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*/];
}
});
});
}
Original file line number Diff line number Diff line change
@@ -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))
}
Original file line number Diff line number Diff line change
@@ -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>
}
26 changes: 26 additions & 0 deletions tests/cases/compiler/destructureOfVariableSameAsShorthand.ts
Original file line number Diff line number Diff line change
@@ -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();
}