Skip to content

Fix inference for contextually typed parameters with initializers #29576

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 8 commits into from
Jan 30, 2019
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
41 changes: 22 additions & 19 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4862,17 +4862,8 @@ namespace ts {
function getTypeForBindingElement(declaration: BindingElement): Type | undefined {
const pattern = declaration.parent;
let parentType = getTypeForBindingElementParent(pattern.parent);
// If parent has the unknown (error) type, then so does this binding element
if (parentType === errorType) {
return errorType;
}
// If no type was specified or inferred for parent,
// infer from the initializer of the binding element if one is present.
// Otherwise, go with the undefined type of the parent.
if (!parentType) {
return declaration.initializer ? checkDeclarationInitializer(declaration) : parentType;
}
if (isTypeAny(parentType)) {
// If no type or an any type was inferred for parent, infer that for the binding element
if (!parentType || isTypeAny(parentType)) {
return parentType;
}
// Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation
Expand Down Expand Up @@ -4958,6 +4949,12 @@ namespace ts {
return strictNullChecks && optional ? getOptionalType(type) : type;
}

function isParameterOfContextuallyTypedFunction(node: Declaration) {
return node.kind === SyntaxKind.Parameter &&
(node.parent.kind === SyntaxKind.FunctionExpression || node.parent.kind === SyntaxKind.ArrowFunction) &&
!!getContextualType(<Expression>node.parent);
}

// Return the inferred type for a variable, parameter, or property declaration
function getTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement, includeOptionality: boolean): Type | undefined {
// A variable declared in a for..in statement is of type string, or of type keyof T when the
Expand Down Expand Up @@ -5041,8 +5038,9 @@ namespace ts {
}
}
Copy link
Member

@weswigham weswigham Jan 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't we rather write declaration.initializer && !(declaration.kind === SyntaxKind.Parameter && getContextualType(<Expression>declaration.parent)) so we only call getContextualType if we absolutely need to?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, since it's used below, write

const needsShapeAndIsNotParameterOfContextuallyTypedFunction = (declaration.initializer || isBindingPattern(declaration.name)) && !(declaration.kind === SyntaxKind.Parameter && getContextualType(<Expression>declaration.parent));

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, possibly. The value is precomputed because I use it in two locations. But I supposed I could turn it into a function that we only call when appropriate.


// Use the type of the initializer expression if one is present
if (declaration.initializer) {
// Use the type of the initializer expression if one is present and the declaration is
// not a parameter of a contextually typed function
if (declaration.initializer && !isParameterOfContextuallyTypedFunction(declaration)) {
const type = checkDeclarationInitializer(declaration);
return addOptionality(type, isOptional);
}
Expand All @@ -5053,8 +5051,9 @@ namespace ts {
return trueType;
}

// If the declaration specifies a binding pattern, use the type implied by the binding pattern
if (isBindingPattern(declaration.name)) {
// If the declaration specifies a binding pattern and is not a parameter of a contextually
// typed function, use the type implied by the binding pattern
if (isBindingPattern(declaration.name) && !isParameterOfContextuallyTypedFunction(declaration)) {
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true);
}

Expand Down Expand Up @@ -5691,17 +5690,21 @@ namespace ts {
}

function reportCircularityError(symbol: Symbol) {
const declaration = <VariableLikeDeclaration>symbol.valueDeclaration;
// Check if variable has type annotation that circularly references the variable itself
if (getEffectiveTypeAnnotationNode(<VariableLikeDeclaration>symbol.valueDeclaration)) {
if (getEffectiveTypeAnnotationNode(declaration)) {
error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation,
symbolToString(symbol));
return errorType;
}
// Otherwise variable has initializer that circularly references the variable itself
if (noImplicitAny) {
// Check if variable has initializer that circularly references the variable itself
if (noImplicitAny && (declaration.kind !== SyntaxKind.Parameter || (<HasInitializer>declaration).initializer)) {
error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer,
symbolToString(symbol));
}
// Circularities could also result from parameters in function expressions that end up
// having themselves as contextual types following type argument inference. In those cases
// we have already reported an implicit any error so we don't report anything here.
return anyType;
}

Expand Down Expand Up @@ -25652,7 +25655,7 @@ namespace ts {
const parent = node.parent.parent;
const parentType = getTypeForBindingElementParent(parent);
const name = node.propertyName || node.name;
if (!isBindingPattern(name) && parentType) {
if (parentType && !isBindingPattern(name)) {
const exprType = getLiteralTypeFromPropertyName(name);
if (isTypeUsableAsPropertyName(exprType)) {
const nameText = getPropertyNameFromType(exprType);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
tests/cases/compiler/contextuallyTypedParametersWithInitializers.ts(8,29): error TS7031: Binding element 'foo' implicitly has an 'any' type.
tests/cases/compiler/contextuallyTypedParametersWithInitializers.ts(14,27): error TS7006: Parameter 'foo' implicitly has an 'any' type.
tests/cases/compiler/contextuallyTypedParametersWithInitializers.ts(27,42): error TS7031: Binding element 'foo' implicitly has an 'any' type.


==== tests/cases/compiler/contextuallyTypedParametersWithInitializers.ts (3 errors) ====
declare function id1<T>(input: T): T;
declare function id2<T extends (x: any) => any>(input: T): T;
declare function id3<T extends (x: { foo: any }) => any>(input: T): T;
declare function id4<T extends (x: { foo?: number }) => any>(input: T): T;
declare function id5<T extends (x?: number) => any>(input: T): T;

const f10 = function ({ foo = 42 }) { return foo };
const f11 = id1(function ({ foo = 42 }) { return foo }); // Implicit any error
~~~
!!! error TS7031: Binding element 'foo' implicitly has an 'any' type.
const f12 = id2(function ({ foo = 42 }) { return foo });
const f13 = id3(function ({ foo = 42 }) { return foo });
const f14 = id4(function ({ foo = 42 }) { return foo });

const f20 = function (foo = 42) { return foo };
const f21 = id1(function (foo = 42) { return foo }); // Implicit any error
~~~~~~~~
!!! error TS7006: Parameter 'foo' implicitly has an 'any' type.
const f22 = id2(function (foo = 42) { return foo });
const f25 = id5(function (foo = 42) { return foo });

// Repro from #28816

function id<T>(input: T): T { return input }

function getFoo ({ foo = 42 }) {
return foo;
}

const newGetFoo = id(getFoo);
const newGetFoo2 = id(function getFoo ({ foo = 42 }) {
~~~
!!! error TS7031: Binding element 'foo' implicitly has an 'any' type.
return foo;
});

Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//// [contextuallyTypedParametersWithInitializers.ts]
declare function id1<T>(input: T): T;
declare function id2<T extends (x: any) => any>(input: T): T;
declare function id3<T extends (x: { foo: any }) => any>(input: T): T;
declare function id4<T extends (x: { foo?: number }) => any>(input: T): T;
declare function id5<T extends (x?: number) => any>(input: T): T;

const f10 = function ({ foo = 42 }) { return foo };
const f11 = id1(function ({ foo = 42 }) { return foo }); // Implicit any error
const f12 = id2(function ({ foo = 42 }) { return foo });
const f13 = id3(function ({ foo = 42 }) { return foo });
const f14 = id4(function ({ foo = 42 }) { return foo });

const f20 = function (foo = 42) { return foo };
const f21 = id1(function (foo = 42) { return foo }); // Implicit any error
const f22 = id2(function (foo = 42) { return foo });
const f25 = id5(function (foo = 42) { return foo });

// Repro from #28816

function id<T>(input: T): T { return input }

function getFoo ({ foo = 42 }) {
return foo;
}

const newGetFoo = id(getFoo);
const newGetFoo2 = id(function getFoo ({ foo = 42 }) {
return foo;
});


//// [contextuallyTypedParametersWithInitializers.js]
"use strict";
var f10 = function (_a) {
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
return foo;
};
var f11 = id1(function (_a) {
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
return foo;
}); // Implicit any error
var f12 = id2(function (_a) {
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
return foo;
});
var f13 = id3(function (_a) {
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
return foo;
});
var f14 = id4(function (_a) {
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
return foo;
});
var f20 = function (foo) {
if (foo === void 0) { foo = 42; }
return foo;
};
var f21 = id1(function (foo) {
if (foo === void 0) { foo = 42; }
return foo;
}); // Implicit any error
var f22 = id2(function (foo) {
if (foo === void 0) { foo = 42; }
return foo;
});
var f25 = id5(function (foo) {
if (foo === void 0) { foo = 42; }
return foo;
});
// Repro from #28816
function id(input) { return input; }
function getFoo(_a) {
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
return foo;
}
var newGetFoo = id(getFoo);
var newGetFoo2 = id(function getFoo(_a) {
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
return foo;
});


//// [contextuallyTypedParametersWithInitializers.d.ts]
declare function id1<T>(input: T): T;
declare function id2<T extends (x: any) => any>(input: T): T;
declare function id3<T extends (x: {
foo: any;
}) => any>(input: T): T;
declare function id4<T extends (x: {
foo?: number;
}) => any>(input: T): T;
declare function id5<T extends (x?: number) => any>(input: T): T;
declare const f10: ({ foo }: {
foo?: number | undefined;
}) => number;
declare const f11: ({ foo }: any) => any;
declare const f12: ({ foo }: any) => any;
declare const f13: ({ foo }: {
foo: any;
}) => any;
declare const f14: ({ foo }: {
foo?: number | undefined;
}) => number;
declare const f20: (foo?: number) => number;
declare const f21: (foo?: any) => any;
declare const f22: (foo?: any) => any;
declare const f25: (foo?: number | undefined) => number;
declare function id<T>(input: T): T;
declare function getFoo({ foo }: {
foo?: number | undefined;
}): number;
declare const newGetFoo: typeof getFoo;
declare const newGetFoo2: ({ foo }: any) => any;
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
=== tests/cases/compiler/contextuallyTypedParametersWithInitializers.ts ===
declare function id1<T>(input: T): T;
>id1 : Symbol(id1, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 0))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 21))
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 24))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 21))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 21))

declare function id2<T extends (x: any) => any>(input: T): T;
>id2 : Symbol(id2, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 37))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 21))
>x : Symbol(x, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 32))
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 48))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 21))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 21))

declare function id3<T extends (x: { foo: any }) => any>(input: T): T;
>id3 : Symbol(id3, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 61))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 21))
>x : Symbol(x, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 32))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 36))
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 57))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 21))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 21))

declare function id4<T extends (x: { foo?: number }) => any>(input: T): T;
>id4 : Symbol(id4, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 70))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 21))
>x : Symbol(x, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 32))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 36))
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 61))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 21))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 21))

declare function id5<T extends (x?: number) => any>(input: T): T;
>id5 : Symbol(id5, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 74))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 4, 21))
>x : Symbol(x, Decl(contextuallyTypedParametersWithInitializers.ts, 4, 32))
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 4, 52))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 4, 21))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 4, 21))

const f10 = function ({ foo = 42 }) { return foo };
>f10 : Symbol(f10, Decl(contextuallyTypedParametersWithInitializers.ts, 6, 5))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 6, 23))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 6, 23))

const f11 = id1(function ({ foo = 42 }) { return foo }); // Implicit any error
>f11 : Symbol(f11, Decl(contextuallyTypedParametersWithInitializers.ts, 7, 5))
>id1 : Symbol(id1, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 0))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 7, 27))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 7, 27))

const f12 = id2(function ({ foo = 42 }) { return foo });
>f12 : Symbol(f12, Decl(contextuallyTypedParametersWithInitializers.ts, 8, 5))
>id2 : Symbol(id2, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 37))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 8, 27))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 8, 27))

const f13 = id3(function ({ foo = 42 }) { return foo });
>f13 : Symbol(f13, Decl(contextuallyTypedParametersWithInitializers.ts, 9, 5))
>id3 : Symbol(id3, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 61))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 9, 27))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 9, 27))

const f14 = id4(function ({ foo = 42 }) { return foo });
>f14 : Symbol(f14, Decl(contextuallyTypedParametersWithInitializers.ts, 10, 5))
>id4 : Symbol(id4, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 70))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 10, 27))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 10, 27))

const f20 = function (foo = 42) { return foo };
>f20 : Symbol(f20, Decl(contextuallyTypedParametersWithInitializers.ts, 12, 5))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 12, 22))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 12, 22))

const f21 = id1(function (foo = 42) { return foo }); // Implicit any error
>f21 : Symbol(f21, Decl(contextuallyTypedParametersWithInitializers.ts, 13, 5))
>id1 : Symbol(id1, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 0))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 13, 26))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 13, 26))

const f22 = id2(function (foo = 42) { return foo });
>f22 : Symbol(f22, Decl(contextuallyTypedParametersWithInitializers.ts, 14, 5))
>id2 : Symbol(id2, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 37))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 14, 26))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 14, 26))

const f25 = id5(function (foo = 42) { return foo });
>f25 : Symbol(f25, Decl(contextuallyTypedParametersWithInitializers.ts, 15, 5))
>id5 : Symbol(id5, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 74))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 15, 26))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 15, 26))

// Repro from #28816

function id<T>(input: T): T { return input }
>id : Symbol(id, Decl(contextuallyTypedParametersWithInitializers.ts, 15, 52))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 12))
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 15))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 12))
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 12))
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 15))

function getFoo ({ foo = 42 }) {
>getFoo : Symbol(getFoo, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 44))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 21, 18))

return foo;
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 21, 18))
}

const newGetFoo = id(getFoo);
>newGetFoo : Symbol(newGetFoo, Decl(contextuallyTypedParametersWithInitializers.ts, 25, 5))
>id : Symbol(id, Decl(contextuallyTypedParametersWithInitializers.ts, 15, 52))
>getFoo : Symbol(getFoo, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 44))

const newGetFoo2 = id(function getFoo ({ foo = 42 }) {
>newGetFoo2 : Symbol(newGetFoo2, Decl(contextuallyTypedParametersWithInitializers.ts, 26, 5))
>id : Symbol(id, Decl(contextuallyTypedParametersWithInitializers.ts, 15, 52))
>getFoo : Symbol(getFoo, Decl(contextuallyTypedParametersWithInitializers.ts, 26, 22))
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 26, 40))

return foo;
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 26, 40))

});

Loading