diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index acf5125aa9b..356ddc2a28e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4849,7 +4849,7 @@ namespace ts { if (strictNullChecks && declaration.initializer && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined)) { type = getTypeWithFacts(type, TypeFacts.NEUndefined); } - return declaration.initializer && !getEffectiveTypeAnnotationNode(walkUpBindingElementsAndPatterns(declaration)) ? + return declaration.initializer && !getContextualTypeForVariableLikeDeclaration(walkUpBindingElementsAndPatterns(declaration)) ? getUnionType([type, checkDeclarationInitializer(declaration)], UnionReduction.Subtype) : type; } @@ -17009,6 +17009,32 @@ namespace ts { } } + function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration): Type | undefined { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + return getTypeFromTypeNode(typeNode); + } + switch (declaration.kind) { + case SyntaxKind.Parameter: + return getContextuallyTypedParameterType(declaration); + case SyntaxKind.BindingElement: + return getContextualTypeForBindingElement(declaration); + // By default, do nothing and return undefined - only parameters and binding elements have context implied by a parent + } + } + + function getContextualTypeForBindingElement(declaration: BindingElement): Type | undefined { + const parentDeclaration = declaration.parent.parent; + const name = declaration.propertyName || declaration.name; + const parentType = getContextualTypeForVariableLikeDeclaration(parentDeclaration); + if (parentType && !isBindingPattern(name)) { + const text = getTextOfPropertyName(name); + if (text !== undefined) { + return getTypeOfPropertyOfType(parentType, text); + } + } + } + // In a variable, parameter or property declaration with a type annotation, // the contextual type of an initializer expression is the type of the variable, parameter or property. // Otherwise, in a parameter declaration of a contextually typed function expression, @@ -17020,32 +17046,13 @@ namespace ts { function getContextualTypeForInitializerExpression(node: Expression): Type | undefined { const declaration = node.parent; if (hasInitializer(declaration) && node === declaration.initializer) { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - return getTypeFromTypeNode(typeNode); + const result = getContextualTypeForVariableLikeDeclaration(declaration); + if (result) { + return result; } - if (declaration.kind === SyntaxKind.Parameter) { - const type = getContextuallyTypedParameterType(declaration); - if (type) { - return type; - } - } - if (isBindingPattern(declaration.name)) { + if (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); } - if (isBindingPattern(declaration.parent)) { - const parentDeclaration = declaration.parent.parent; - const name = (declaration as BindingElement).propertyName || declaration.name; - if (parentDeclaration.kind !== SyntaxKind.BindingElement) { - const parentTypeNode = getEffectiveTypeAnnotationNode(parentDeclaration); - if (parentTypeNode && !isBindingPattern(name)) { - const text = getTextOfPropertyName(name); - if (text) { - return getTypeOfPropertyOfType(getTypeFromTypeNode(parentTypeNode), text); - } - } - } - } } return undefined; } diff --git a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types index 0fb81ba6aa3..389b6b5e5f5 100644 --- a/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types +++ b/tests/baselines/reference/destructuringArrayBindingPatternAndAssignment3.types @@ -45,8 +45,8 @@ const [f, g = f, h = i, i = f] = [1]; // error for h = i >c : number >d : number >c : number ->e : any ->e : any +>e : number +>e : number })([1]); >[1] : number[] diff --git a/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.js b/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.js new file mode 100644 index 00000000000..2dd6b4ea11a --- /dev/null +++ b/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.js @@ -0,0 +1,50 @@ +//// [destructuringInitializerContextualTypeFromContext.ts] +interface SFC

{ + (props: P & { children?: any }): any | null; +} + +interface Props { + name: "Apollo" | "Artemis" | "Dionysus" | "Persephone"; +} + +const Parent: SFC = ({ + children, + name = "Artemis", + ...props +}) => Child({name, ...props}); + +const Child: SFC = ({ + children, + name = "Artemis", + ...props +}) => `name: ${name} props: ${JSON.stringify(props)}`; + +//// [destructuringInitializerContextualTypeFromContext.js] +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) + t[p[i]] = s[p[i]]; + return t; +}; +var Parent = function (_a) { + var children = _a.children, _b = _a.name, name = _b === void 0 ? "Artemis" : _b, props = __rest(_a, ["children", "name"]); + return Child(__assign({ name: name }, props)); +}; +var Child = function (_a) { + var children = _a.children, _b = _a.name, name = _b === void 0 ? "Artemis" : _b, props = __rest(_a, ["children", "name"]); + return "name: " + name + " props: " + JSON.stringify(props); +}; diff --git a/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.symbols b/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.symbols new file mode 100644 index 00000000000..317a09ee105 --- /dev/null +++ b/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.symbols @@ -0,0 +1,58 @@ +=== tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts === +interface SFC

{ +>SFC : Symbol(SFC, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 0)) +>P : Symbol(P, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 14)) + + (props: P & { children?: any }): any | null; +>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 1, 5)) +>P : Symbol(P, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 14)) +>children : Symbol(children, Decl(destructuringInitializerContextualTypeFromContext.ts, 1, 17)) +} + +interface Props { +>Props : Symbol(Props, Decl(destructuringInitializerContextualTypeFromContext.ts, 2, 1)) + + name: "Apollo" | "Artemis" | "Dionysus" | "Persephone"; +>name : Symbol(Props.name, Decl(destructuringInitializerContextualTypeFromContext.ts, 4, 17)) +} + +const Parent: SFC = ({ +>Parent : Symbol(Parent, Decl(destructuringInitializerContextualTypeFromContext.ts, 8, 5)) +>SFC : Symbol(SFC, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 0)) +>Props : Symbol(Props, Decl(destructuringInitializerContextualTypeFromContext.ts, 2, 1)) + + children, +>children : Symbol(children, Decl(destructuringInitializerContextualTypeFromContext.ts, 8, 29)) + + name = "Artemis", +>name : Symbol(name, Decl(destructuringInitializerContextualTypeFromContext.ts, 9, 13)) + + ...props +>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 10, 21)) + +}) => Child({name, ...props}); +>Child : Symbol(Child, Decl(destructuringInitializerContextualTypeFromContext.ts, 14, 5)) +>name : Symbol(name, Decl(destructuringInitializerContextualTypeFromContext.ts, 12, 13)) +>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 10, 21)) + +const Child: SFC = ({ +>Child : Symbol(Child, Decl(destructuringInitializerContextualTypeFromContext.ts, 14, 5)) +>SFC : Symbol(SFC, Decl(destructuringInitializerContextualTypeFromContext.ts, 0, 0)) +>Props : Symbol(Props, Decl(destructuringInitializerContextualTypeFromContext.ts, 2, 1)) + + children, +>children : Symbol(children, Decl(destructuringInitializerContextualTypeFromContext.ts, 14, 28)) + + name = "Artemis", +>name : Symbol(name, Decl(destructuringInitializerContextualTypeFromContext.ts, 15, 13)) + + ...props +>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 16, 21)) + +}) => `name: ${name} props: ${JSON.stringify(props)}`; +>name : Symbol(name, Decl(destructuringInitializerContextualTypeFromContext.ts, 15, 13)) +>JSON.stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>props : Symbol(props, Decl(destructuringInitializerContextualTypeFromContext.ts, 16, 21)) + diff --git a/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.types b/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.types new file mode 100644 index 00000000000..fa76e2eef25 --- /dev/null +++ b/tests/baselines/reference/destructuringInitializerContextualTypeFromContext.types @@ -0,0 +1,57 @@ +=== tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts === +interface SFC

{ + (props: P & { children?: any }): any | null; +>props : P & { children?: any; } +>children : any +>null : null +} + +interface Props { + name: "Apollo" | "Artemis" | "Dionysus" | "Persephone"; +>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone" +} + +const Parent: SFC = ({ +>Parent : SFC +>({ children, name = "Artemis", ...props}) => Child({name, ...props}) : ({ children, name, ...props }: Props & { children?: any; }) => any + + children, +>children : any + + name = "Artemis", +>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone" +>"Artemis" : "Artemis" + + ...props +>props : {} + +}) => Child({name, ...props}); +>Child({name, ...props}) : any +>Child : SFC +>{name, ...props} : { name: "Apollo" | "Artemis" | "Dionysus" | "Persephone"; } +>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone" +>props : {} + +const Child: SFC = ({ +>Child : SFC +>({ children, name = "Artemis", ...props}) => `name: ${name} props: ${JSON.stringify(props)}` : ({ children, name, ...props }: Props & { children?: any; }) => string + + children, +>children : any + + name = "Artemis", +>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone" +>"Artemis" : "Artemis" + + ...props +>props : {} + +}) => `name: ${name} props: ${JSON.stringify(props)}`; +>`name: ${name} props: ${JSON.stringify(props)}` : string +>name : "Apollo" | "Artemis" | "Dionysus" | "Persephone" +>JSON.stringify(props) : string +>JSON.stringify : { (value: any, replacer?: (key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (string | number)[], space?: string | number): string; } +>JSON : JSON +>stringify : { (value: any, replacer?: (key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (string | number)[], space?: string | number): string; } +>props : {} + diff --git a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types index 497bae6993a..7ffbc43b86a 100644 --- a/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types +++ b/tests/baselines/reference/optionalParameterInDestructuringWithInitializer.types @@ -167,7 +167,7 @@ function func7( {a: {b, c = 6} = {b: 4, c: 5}, d}: {a: {b: number, c?: number}, >b : number >c : number >6 : 6 ->{b: 4, c: 5} : { b: number; c?: number; } +>{b: 4, c: 5} : { b: number; c: number; } >b : number >4 : 4 >c : number diff --git a/tests/baselines/reference/sourceMapValidationDestructuringParameterNestedObjectBindingPatternDefaultValues.types b/tests/baselines/reference/sourceMapValidationDestructuringParameterNestedObjectBindingPatternDefaultValues.types index 82c3c8d35a6..8bfc93c3db7 100644 --- a/tests/baselines/reference/sourceMapValidationDestructuringParameterNestedObjectBindingPatternDefaultValues.types +++ b/tests/baselines/reference/sourceMapValidationDestructuringParameterNestedObjectBindingPatternDefaultValues.types @@ -50,7 +50,7 @@ function foo1( >"secondary" : "secondary" } = { primary: "SomeSkill", secondary: "someSkill" } ->{ primary: "SomeSkill", secondary: "someSkill" } : { primary?: string; secondary?: string; } +>{ primary: "SomeSkill", secondary: "someSkill" } : { primary: string; secondary: string; } >primary : string >"SomeSkill" : "SomeSkill" >secondary : string @@ -88,7 +88,7 @@ function foo2( >"secondary" : "secondary" } = { primary: "SomeSkill", secondary: "someSkill" } ->{ primary: "SomeSkill", secondary: "someSkill" } : { primary?: string; secondary?: string; } +>{ primary: "SomeSkill", secondary: "someSkill" } : { primary: string; secondary: string; } >primary : string >"SomeSkill" : "SomeSkill" >secondary : string diff --git a/tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts b/tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts new file mode 100644 index 00000000000..6153b1e3a80 --- /dev/null +++ b/tests/cases/compiler/destructuringInitializerContextualTypeFromContext.ts @@ -0,0 +1,19 @@ +interface SFC

{ + (props: P & { children?: any }): any | null; +} + +interface Props { + name: "Apollo" | "Artemis" | "Dionysus" | "Persephone"; +} + +const Parent: SFC = ({ + children, + name = "Artemis", + ...props +}) => Child({name, ...props}); + +const Child: SFC = ({ + children, + name = "Artemis", + ...props +}) => `name: ${name} props: ${JSON.stringify(props)}`; \ No newline at end of file