From 1e9939fcd4439950dc9059709df9bec75c8d1bc8 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 31 Jan 2018 15:02:35 -0800 Subject: [PATCH 01/10] Used to work, but incorrectly. Now it fails to compile Not sure why, so the code is currently scratched up with investigation. --- src/compiler/binder.ts | 61 +++++++++++++++++++++++++++++++++-------- src/compiler/checker.ts | 32 ++++++++++++++++++++- src/compiler/types.ts | 7 +++++ 3 files changed, 87 insertions(+), 13 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d366ff3dfe524..15c2af1455a63 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -845,6 +845,11 @@ namespace ts { return { flags: FlowFlags.Assignment, antecedent, node }; } + function createFlowInitializer(antecedent: FlowNode, node: Expression | VariableDeclaration | BindingElement): FlowNode { + setFlowNodeReferenced(antecedent); + return { flags: FlowFlags.Initializer, antecedent, node }; + } + function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode { setFlowNodeReferenced(antecedent); const res: FlowArrayMutation = { flags: FlowFlags.ArrayMutation, antecedent, node }; @@ -1094,8 +1099,9 @@ namespace ts { // // extra edges that we inject allows to control this behavior // if when walking the flow we step on post-finally edge - we can mark matching pre-finally edge as locked so it will be skipped. - const preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preTryFlow, lock: {} }; - addAntecedent(preFinallyLabel, preFinallyFlow); + let preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preTryFlow, lock: {} }; + preFinallyFlow = localInit(preTryFlow, preFinallyLabel, true); + // addAntecedent(preFinallyLabel, preFinallyFlow); currentFlow = finishFlowLabel(preFinallyLabel); bind(node.finallyBlock); @@ -1123,6 +1129,19 @@ namespace ts { } } + function localInit(preTryFlow: FlowNode, preFinallyLabel: FlowLabel, finallyBlock: boolean): PreFinallyFlow { + if (finallyBlock) { + return preFinallyFlow; + } + const preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preTryFlow, lock: {} }; + addAntecedent(preFinallyLabel, preFinallyFlow); + if (!finallyBlock) { + const afterFinallyFlow: AfterFinallyFlow = { flags: FlowFlags.AfterFinally, antecedent: currentFlow }; + preFinallyFlow.lock = afterFinallyFlow; + } + return preFinallyFlow; + } + function bindSwitchStatement(node: SwitchStatement): void { const postSwitchLabel = createBranchLabel(); bind(node.expression); @@ -1211,39 +1230,49 @@ namespace ts { } } - function bindDestructuringTargetFlow(node: Expression) { + function bindDestructuringTargetFlow(node: Expression, SPECIAL?: boolean) { if (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.EqualsToken) { - bindAssignmentTargetFlow((node).left); + bindAssignmentTargetFlow((node).left, SPECIAL); } else { - bindAssignmentTargetFlow(node); + bindAssignmentTargetFlow(node, SPECIAL); } } - function bindAssignmentTargetFlow(node: Expression) { + function bindAssignmentTargetFlow(node: Expression, SPECIAL?: boolean) { if (isNarrowableReference(node)) { - currentFlow = createFlowAssignment(currentFlow, node); + if (SPECIAL) { + currentFlow = createFlowInitializer(currentFlow, node); + } + else { + currentFlow = createFlowAssignment(currentFlow, node); + } } else if (node.kind === SyntaxKind.ArrayLiteralExpression) { for (const e of (node).elements) { if (e.kind === SyntaxKind.SpreadElement) { - bindAssignmentTargetFlow((e).expression); + bindAssignmentTargetFlow((e).expression, SPECIAL); } else { - bindDestructuringTargetFlow(e); + bindDestructuringTargetFlow(e, SPECIAL); } } } else if (node.kind === SyntaxKind.ObjectLiteralExpression) { for (const p of (node).properties) { if (p.kind === SyntaxKind.PropertyAssignment) { - bindDestructuringTargetFlow((p).initializer); + if (SPECIAL && p.name.kind === SyntaxKind.Identifier) { + bindAssignmentTargetFlow(p.name, SPECIAL); + } + else { + bindDestructuringTargetFlow(p.initializer, SPECIAL); + } } else if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { - bindAssignmentTargetFlow((p).name); + bindAssignmentTargetFlow(p.name, SPECIAL); } else if (p.kind === SyntaxKind.SpreadAssignment) { - bindAssignmentTargetFlow((p).expression); + bindAssignmentTargetFlow(p.expression, SPECIAL); } } } @@ -1344,6 +1373,14 @@ namespace ts { } else { currentFlow = createFlowAssignment(currentFlow, node); + if (isVariableDeclaration(node) && + node.type && + node.initializer && + isIdentifier(node.name) && + node.initializer.kind === SyntaxKind.ObjectLiteralExpression) { + // should be fine! + bindAssignmentTargetFlow(node.initializer as ObjectLiteralExpression, /*SPECIAL*/ true); + } } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8b57947438273..48573ed2cae28 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12239,6 +12239,13 @@ namespace ts { continue; } } + else if (flags & FlowFlags.Initializer) { + type = getTypeAtFlowInitializer(flow as FlowInitializer); + if (!type) { + flow = (flow).antecedent; + continue; + } + } else if (flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); } @@ -12287,6 +12294,30 @@ namespace ts { } } + function getTypeAtFlowInitializer(flow: FlowInitializer): Type { + const node = flow.node; + // TODO: More conditions here to do with object literals and variable declarations + // (and actually recursive ones) + // node identifier=bar + // .parent propertyassignment=bar: [] + // .parent objectliteral= { bar: [] } + // .parent variabledeclaration = f2: Foo = { bar: [] } + // .name identifier f2 + if (isPropertyAccessExpression(reference) && + isPropertyAssignment(node.parent) && + isIdentifier(node) && + isIdentifier((node.parent.parent.parent as VariableDeclaration).name) && + // isMatchingReference(reference, createPropertyAccess(reference.expression, node))) { + isMatchingReference(reference, createPropertyAccess((node.parent.parent.parent as VariableDeclaration).name as Identifier, node))) { + if (declaredType.flags & TypeFlags.Union) { + return getAssignmentReducedType(declaredType as UnionType, getTypeOfNode((node.parent as PropertyAssignment).initializer)); + } + return declaredType; + } + // initializer doesn't affect reference + return undefined; + } + function getTypeAtFlowAssignment(flow: FlowAssignment) { const node = flow.node; // Assignments only narrow the computed type if the declared type is a union type. Thus, we @@ -12315,7 +12346,6 @@ namespace ts { if (containsMatchingReference(reference, node)) { return declaredType; } - // Assignment doesn't affect reference return undefined; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 481b91c9276e1..e219fb02e1784 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2363,6 +2363,7 @@ namespace ts { Shared = 1 << 10, // Referenced as antecedent more than once PreFinally = 1 << 11, // Injected edge that links pre-finally label and pre-try flow AfterFinally = 1 << 12, // Injected edge that links post-finally flow with the rest of the graph + Initializer = 1 << 13, // Property assignment inside object literal, intended for contextually-typed objects Label = BranchLabel | LoopLabel, Condition = TrueCondition | FalseCondition } @@ -2406,6 +2407,12 @@ namespace ts { antecedent: FlowNode; } + // a huge clone of FlowAssignment. Probably not needed! + export interface FlowInitializer extends FlowNodeBase { + node: Expression | VariableDeclaration | BindingElement; + antecedent: FlowNode; + } + // FlowCondition represents a condition that is known to be true or false at the // node's location in the control flow. export interface FlowCondition extends FlowNodeBase { From 23fb403c4b5e66332b12665a4e8e962950b4fe21 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 31 Jan 2018 15:03:36 -0800 Subject: [PATCH 02/10] Test:narrowing by property assignment in obj literal --- .../reference/controlFlowDeleteOperator.types | 4 +-- .../controlFlowObjectLiteralDeclaration.js | 14 +++++++++++ ...ontrolFlowObjectLiteralDeclaration.symbols | 21 ++++++++++++++++ .../controlFlowObjectLiteralDeclaration.types | 25 +++++++++++++++++++ .../destructuringTypeGuardFlow.types | 4 +-- .../reference/typeGuardsOnClassProperty.types | 12 ++++----- .../controlFlowObjectLiteralDeclaration.ts | 8 ++++++ 7 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 tests/baselines/reference/controlFlowObjectLiteralDeclaration.js create mode 100644 tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols create mode 100644 tests/baselines/reference/controlFlowObjectLiteralDeclaration.types create mode 100644 tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts diff --git a/tests/baselines/reference/controlFlowDeleteOperator.types b/tests/baselines/reference/controlFlowDeleteOperator.types index 7c8cfc8dd6117..7836c41bf6e7d 100644 --- a/tests/baselines/reference/controlFlowDeleteOperator.types +++ b/tests/baselines/reference/controlFlowDeleteOperator.types @@ -16,9 +16,9 @@ function f() { >a : string | number | undefined x.b; ->x.b : string | number +>x.b : number >x : { a?: string | number | undefined; b: string | number; } ->b : string | number +>b : number x.a = 1; >x.a = 1 : 1 diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js new file mode 100644 index 0000000000000..e59d1cba0c2c5 --- /dev/null +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js @@ -0,0 +1,14 @@ +//// [controlFlowObjectLiteralDeclaration.ts] +type Foo = { + bar?: number[]; +} + +let f2: Foo = { bar: [] }; +f2.bar.push(0) + + + +//// [controlFlowObjectLiteralDeclaration.js] +"use strict"; +var f2 = { bar: [] }; +f2.bar.push(0); diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols new file mode 100644 index 0000000000000..362e46994b143 --- /dev/null +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols @@ -0,0 +1,21 @@ +=== tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts === +type Foo = { +>Foo : Symbol(Foo, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 0)) + + bar?: number[]; +>bar : Symbol(bar, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 12)) +} + +let f2: Foo = { bar: [] }; +>f2 : Symbol(f2, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 3)) +>Foo : Symbol(Foo, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 0)) +>bar : Symbol(bar, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 15)) + +f2.bar.push(0) +>f2.bar.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>f2.bar : Symbol(bar, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 12)) +>f2 : Symbol(f2, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 3)) +>bar : Symbol(bar, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 12)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + + diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types new file mode 100644 index 0000000000000..ecc4f5eedfc96 --- /dev/null +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types @@ -0,0 +1,25 @@ +=== tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts === +type Foo = { +>Foo : Foo + + bar?: number[]; +>bar : number[] | undefined +} + +let f2: Foo = { bar: [] }; +>f2 : Foo +>Foo : Foo +>{ bar: [] } : { bar: never[]; } +>bar : never[] +>[] : never[] + +f2.bar.push(0) +>f2.bar.push(0) : number +>f2.bar.push : (...items: number[]) => number +>f2.bar : number[] +>f2 : Foo +>bar : number[] +>push : (...items: number[]) => number +>0 : 0 + + diff --git a/tests/baselines/reference/destructuringTypeGuardFlow.types b/tests/baselines/reference/destructuringTypeGuardFlow.types index e04dfa645c9ca..702fd3d897bb2 100644 --- a/tests/baselines/reference/destructuringTypeGuardFlow.types +++ b/tests/baselines/reference/destructuringTypeGuardFlow.types @@ -38,9 +38,9 @@ const aFoo: foo = { bar: 3, baz: "b", nested: { a: 1, b: "y" } }; if (aFoo.bar && aFoo.nested.b) { >aFoo.bar && aFoo.nested.b : string | 0 | null ->aFoo.bar : number | null +>aFoo.bar : number >aFoo : foo ->bar : number | null +>bar : number >aFoo.nested.b : string | null >aFoo.nested : { a: number; b: string | null; } >aFoo : foo diff --git a/tests/baselines/reference/typeGuardsOnClassProperty.types b/tests/baselines/reference/typeGuardsOnClassProperty.types index e08da7f1a9692..b4df83f353e13 100644 --- a/tests/baselines/reference/typeGuardsOnClassProperty.types +++ b/tests/baselines/reference/typeGuardsOnClassProperty.types @@ -82,9 +82,9 @@ if (typeof o.prop1 === "string" && o.prop1.toLowerCase()) {} >typeof o.prop1 === "string" && o.prop1.toLowerCase() : string >typeof o.prop1 === "string" : boolean >typeof o.prop1 : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" ->o.prop1 : string | number +>o.prop1 : string >o : { prop1: string | number; prop2: string | boolean; } ->prop1 : string | number +>prop1 : string >"string" : "string" >o.prop1.toLowerCase() : string >o.prop1.toLowerCase : () => string @@ -94,16 +94,16 @@ if (typeof o.prop1 === "string" && o.prop1.toLowerCase()) {} >toLowerCase : () => string var prop1 = o.prop1; ->prop1 : string | number ->o.prop1 : string | number +>prop1 : string +>o.prop1 : string >o : { prop1: string | number; prop2: string | boolean; } ->prop1 : string | number +>prop1 : string if (typeof prop1 === "string" && prop1.toLocaleLowerCase()) { } >typeof prop1 === "string" && prop1.toLocaleLowerCase() : string >typeof prop1 === "string" : boolean >typeof prop1 : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function" ->prop1 : string | number +>prop1 : string >"string" : "string" >prop1.toLocaleLowerCase() : string >prop1.toLocaleLowerCase : () => string diff --git a/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts b/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts new file mode 100644 index 0000000000000..ac641af0a967f --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts @@ -0,0 +1,8 @@ +// @strict: true +type Foo = { + bar?: number[]; +} + +let f2: Foo = { bar: [] }; +f2.bar.push(0) + From fdce355008df8dbe57576451bb5afdbeb3ab06c9 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 6 Feb 2018 15:09:11 -0800 Subject: [PATCH 03/10] Use correct references 1. Requires its own FlowFlag because isMatchingReference assumes that it's operating only on references or variable declarations, not initializers. 2. Has a bunch of ugly plumbing in the binder's bindAssignmentTargetFlow; probably it could be simplified. 3. Doesn't add or check control flow nodes recursively, so only top-level property initialisers narrow. --- src/compiler/binder.ts | 20 +++---------------- src/compiler/checker.ts | 11 +++++----- .../reference/api/tsserverlibrary.d.ts | 5 +++++ tests/baselines/reference/api/typescript.d.ts | 5 +++++ 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 15c2af1455a63..c30274a60c4f4 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1099,9 +1099,8 @@ namespace ts { // // extra edges that we inject allows to control this behavior // if when walking the flow we step on post-finally edge - we can mark matching pre-finally edge as locked so it will be skipped. - let preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preTryFlow, lock: {} }; - preFinallyFlow = localInit(preTryFlow, preFinallyLabel, true); - // addAntecedent(preFinallyLabel, preFinallyFlow); + const preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preTryFlow, lock: {} }; + addAntecedent(preFinallyLabel, preFinallyFlow); currentFlow = finishFlowLabel(preFinallyLabel); bind(node.finallyBlock); @@ -1129,19 +1128,6 @@ namespace ts { } } - function localInit(preTryFlow: FlowNode, preFinallyLabel: FlowLabel, finallyBlock: boolean): PreFinallyFlow { - if (finallyBlock) { - return preFinallyFlow; - } - const preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preTryFlow, lock: {} }; - addAntecedent(preFinallyLabel, preFinallyFlow); - if (!finallyBlock) { - const afterFinallyFlow: AfterFinallyFlow = { flags: FlowFlags.AfterFinally, antecedent: currentFlow }; - preFinallyFlow.lock = afterFinallyFlow; - } - return preFinallyFlow; - } - function bindSwitchStatement(node: SwitchStatement): void { const postSwitchLabel = createBranchLabel(); bind(node.expression); @@ -1230,7 +1216,7 @@ namespace ts { } } - function bindDestructuringTargetFlow(node: Expression, SPECIAL?: boolean) { + function bindDestructuringTargetFlow(node: Expression, SPECIAL: boolean) { if (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.EqualsToken) { bindAssignmentTargetFlow((node).left, SPECIAL); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 48573ed2cae28..b6b88730cd44c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12303,12 +12303,11 @@ namespace ts { // .parent objectliteral= { bar: [] } // .parent variabledeclaration = f2: Foo = { bar: [] } // .name identifier f2 - if (isPropertyAccessExpression(reference) && - isPropertyAssignment(node.parent) && - isIdentifier(node) && - isIdentifier((node.parent.parent.parent as VariableDeclaration).name) && - // isMatchingReference(reference, createPropertyAccess(reference.expression, node))) { - isMatchingReference(reference, createPropertyAccess((node.parent.parent.parent as VariableDeclaration).name as Identifier, node))) { + const referenceMatchesPropertyAssignment = isPropertyAccessExpression(reference) && isIdentifier(node) && + isPropertyAssignment(node.parent) && reference.name.escapedText === node.escapedText; + if (referenceMatchesPropertyAssignment && + isVariableDeclaration(node.parent.parent.parent) && + isMatchingReference((reference as PropertyAccessExpression).expression, node.parent.parent.parent)) { if (declaredType.flags & TypeFlags.Union) { return getAssignmentReducedType(declaredType as UnionType, getTypeOfNode((node.parent as PropertyAssignment).initializer)); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 78f68a80c501e..9093e06edf21b 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1528,6 +1528,7 @@ declare namespace ts { Shared = 1024, PreFinally = 2048, AfterFinally = 4096, + Initializer = 8192, Label = 12, Condition = 96, } @@ -1556,6 +1557,10 @@ declare namespace ts { node: Expression | VariableDeclaration | BindingElement; antecedent: FlowNode; } + interface FlowInitializer extends FlowNodeBase { + node: Expression | VariableDeclaration | BindingElement; + antecedent: FlowNode; + } interface FlowCondition extends FlowNodeBase { expression: Expression; antecedent: FlowNode; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index bdcc29af76e36..5b422a34b9039 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1528,6 +1528,7 @@ declare namespace ts { Shared = 1024, PreFinally = 2048, AfterFinally = 4096, + Initializer = 8192, Label = 12, Condition = 96, } @@ -1556,6 +1557,10 @@ declare namespace ts { node: Expression | VariableDeclaration | BindingElement; antecedent: FlowNode; } + interface FlowInitializer extends FlowNodeBase { + node: Expression | VariableDeclaration | BindingElement; + antecedent: FlowNode; + } interface FlowCondition extends FlowNodeBase { expression: Expression; antecedent: FlowNode; From 27c7b97238bef627f291433f89b33165f8865f39 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 16 Feb 2018 08:36:02 -0800 Subject: [PATCH 04/10] Narrow ShorthandPropertyAssignment too --- src/compiler/checker.ts | 9 +- ...rolFlowObjectLiteralDeclaration.errors.txt | 33 ++++++ .../controlFlowObjectLiteralDeclaration.js | 47 +++++++- ...ontrolFlowObjectLiteralDeclaration.symbols | 93 ++++++++++++--- .../controlFlowObjectLiteralDeclaration.types | 110 +++++++++++++++--- .../controlFlowObjectLiteralDeclaration.ts | 27 ++++- 6 files changed, 277 insertions(+), 42 deletions(-) create mode 100644 tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a63b81d9ca2fb..b8fd05ad99e18 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12694,13 +12694,16 @@ namespace ts { // .parent objectliteral= { bar: [] } // .parent variabledeclaration = f2: Foo = { bar: [] } // .name identifier f2 - const referenceMatchesPropertyAssignment = isPropertyAccessExpression(reference) && isIdentifier(node) && - isPropertyAssignment(node.parent) && reference.name.escapedText === node.escapedText; + const referenceMatchesPropertyAssignment = isPropertyAccessExpression(reference) && + isIdentifier(node) && + (isShorthandPropertyAssignment(node.parent) || isPropertyAssignment(node.parent)) && + reference.name.escapedText === node.escapedText; if (referenceMatchesPropertyAssignment && isVariableDeclaration(node.parent.parent.parent) && isMatchingReference((reference as PropertyAccessExpression).expression, node.parent.parent.parent)) { if (declaredType.flags & TypeFlags.Union) { - return getAssignmentReducedType(declaredType as UnionType, getTypeOfNode((node.parent as PropertyAssignment).initializer)); + const sourceNode = isPropertyAssignment(node.parent) ? node.parent.initializer : (node.parent as ShorthandPropertyAssignment).name; + return getAssignmentReducedType(declaredType as UnionType, getTypeOfNode(sourceNode)); } return declaredType; } diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt new file mode 100644 index 0000000000000..a979ead7f7000 --- /dev/null +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt @@ -0,0 +1,33 @@ +tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(24,1): error TS2532: Object is possibly 'undefined'. + + +==== tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts (1 errors) ==== + type A = { + x?: string[] + y?: number[] + z?: { + ka?: boolean + ki?: boolean + } + extra?: string + } + const y = [1, 2, 3] + const wat = { extra: "life" } + let a: A = { + x: [], + y, + z: { + ka: false + }, + ...wat + } + a.x.push('hi') + a.y.push(4) + let b = a.z.ka + b = a.z.ki // error, object is possibly undefined + a.extra.length // error, reference doesn't match 'wat', so object is possibly undefined + ~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. + + + \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js index e59d1cba0c2c5..61dcfb2d1ecd9 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js @@ -1,14 +1,49 @@ //// [controlFlowObjectLiteralDeclaration.ts] -type Foo = { - bar?: number[]; +type A = { + x?: string[] + y?: number[] + z?: { + ka?: boolean + ki?: boolean + } + extra?: string } +const y = [1, 2, 3] +const wat = { extra: "life" } +let a: A = { + x: [], + y, + z: { + ka: false + }, + ...wat +} +a.x.push('hi') +a.y.push(4) +let b = a.z.ka +b = a.z.ki // error, object is possibly undefined +a.extra.length // error, reference doesn't match 'wat', so object is possibly undefined -let f2: Foo = { bar: [] }; -f2.bar.push(0) //// [controlFlowObjectLiteralDeclaration.js] "use strict"; -var f2 = { bar: [] }; -f2.bar.push(0); +var __assign = (this && this.__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; +}; +var y = [1, 2, 3]; +var wat = { extra: "life" }; +var a = __assign({ x: [], y: y, z: { + ka: false + } }, wat); +a.x.push('hi'); +a.y.push(4); +var b = a.z.ka; +b = a.z.ki; // error, object is possibly undefined +a.extra.length; // error, reference doesn't match 'wat', so object is possibly undefined diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols index 362e46994b143..53816496916b5 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols @@ -1,21 +1,88 @@ === tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts === -type Foo = { ->Foo : Symbol(Foo, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 0)) +type A = { +>A : Symbol(A, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 0)) - bar?: number[]; ->bar : Symbol(bar, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 12)) + x?: string[] +>x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 10)) + + y?: number[] +>y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 1, 16)) + + z?: { +>z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) + + ka?: boolean +>ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 3, 9)) + + ki?: boolean +>ki : Symbol(ki, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 20)) + } + extra?: string +>extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 6, 5)) } +const y = [1, 2, 3] +>y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 9, 5)) + +const wat = { extra: "life" } +>wat : Symbol(wat, Decl(controlFlowObjectLiteralDeclaration.ts, 10, 5)) +>extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 10, 13)) + +let a: A = { +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 3)) +>A : Symbol(A, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 0)) + + x: [], +>x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 12)) + + y, +>y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 10)) -let f2: Foo = { bar: [] }; ->f2 : Symbol(f2, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 3)) ->Foo : Symbol(Foo, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 0)) ->bar : Symbol(bar, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 15)) + z: { +>z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 13, 6)) -f2.bar.push(0) ->f2.bar.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) ->f2.bar : Symbol(bar, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 12)) ->f2 : Symbol(f2, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 3)) ->bar : Symbol(bar, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 12)) + ka: false +>ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 14, 8)) + + }, + ...wat +>wat : Symbol(wat, Decl(controlFlowObjectLiteralDeclaration.ts, 10, 5)) +} +a.x.push('hi') +>a.x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>a.x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 10)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 3)) +>x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 10)) +>push : Symbol(Array.push, Decl(lib.d.ts, --, --)) + +a.y.push(4) +>a.y.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +>a.y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 1, 16)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 3)) +>y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 1, 16)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) +let b = a.z.ka +>b : Symbol(b, Decl(controlFlowObjectLiteralDeclaration.ts, 21, 3)) +>a.z.ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 3, 9)) +>a.z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 3)) +>z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) +>ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 3, 9)) + +b = a.z.ki // error, object is possibly undefined +>b : Symbol(b, Decl(controlFlowObjectLiteralDeclaration.ts, 21, 3)) +>a.z.ki : Symbol(ki, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 20)) +>a.z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 3)) +>z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) +>ki : Symbol(ki, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 20)) + +a.extra.length // error, reference doesn't match 'wat', so object is possibly undefined +>a.extra.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>a.extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 6, 5)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 3)) +>extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 6, 5)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) + + diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types index ecc4f5eedfc96..06d0536319c3d 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types @@ -1,25 +1,103 @@ === tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts === -type Foo = { ->Foo : Foo +type A = { +>A : A - bar?: number[]; ->bar : number[] | undefined + x?: string[] +>x : string[] | undefined + + y?: number[] +>y : number[] | undefined + + z?: { +>z : { ka?: boolean | undefined; ki?: boolean | undefined; } | undefined + + ka?: boolean +>ka : boolean | undefined + + ki?: boolean +>ki : boolean | undefined + } + extra?: string +>extra : string | undefined } +const y = [1, 2, 3] +>y : number[] +>[1, 2, 3] : number[] +>1 : 1 +>2 : 2 +>3 : 3 -let f2: Foo = { bar: [] }; ->f2 : Foo ->Foo : Foo ->{ bar: [] } : { bar: never[]; } ->bar : never[] +const wat = { extra: "life" } +>wat : { extra: string; } +>{ extra: "life" } : { extra: string; } +>extra : string +>"life" : "life" + +let a: A = { +>a : A +>A : A +>{ x: [], y, z: { ka: false }, ...wat} : { extra: string; x: never[]; y: number[]; z: { ka: false; }; } + + x: [], +>x : never[] >[] : never[] -f2.bar.push(0) ->f2.bar.push(0) : number ->f2.bar.push : (...items: number[]) => number ->f2.bar : number[] ->f2 : Foo ->bar : number[] + y, +>y : number[] + + z: { +>z : { ka: false; } +>{ ka: false } : { ka: false; } + + ka: false +>ka : false +>false : false + + }, + ...wat +>wat : { extra: string; } +} +a.x.push('hi') +>a.x.push('hi') : number +>a.x.push : (...items: string[]) => number +>a.x : string[] +>a : A +>x : string[] +>push : (...items: string[]) => number +>'hi' : "hi" + +a.y.push(4) +>a.y.push(4) : number +>a.y.push : (...items: number[]) => number +>a.y : number[] +>a : A +>y : number[] >push : (...items: number[]) => number ->0 : 0 +>4 : 4 + +let b = a.z.ka +>b : boolean | undefined +>a.z.ka : boolean | undefined +>a.z : { ka?: boolean | undefined; ki?: boolean | undefined; } +>a : A +>z : { ka?: boolean | undefined; ki?: boolean | undefined; } +>ka : boolean | undefined + +b = a.z.ki // error, object is possibly undefined +>b = a.z.ki : boolean | undefined +>b : boolean | undefined +>a.z.ki : boolean | undefined +>a.z : { ka?: boolean | undefined; ki?: boolean | undefined; } +>a : A +>z : { ka?: boolean | undefined; ki?: boolean | undefined; } +>ki : boolean | undefined + +a.extra.length // error, reference doesn't match 'wat', so object is possibly undefined +>a.extra.length : number +>a.extra : string | undefined +>a : A +>extra : string | undefined +>length : number + diff --git a/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts b/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts index ac641af0a967f..d72af4b0ce5d5 100644 --- a/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts +++ b/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts @@ -1,8 +1,27 @@ // @strict: true -type Foo = { - bar?: number[]; +type A = { + x?: string[] + y?: number[] + z?: { + ka?: boolean + ki?: boolean + } + extra?: string } +const y = [1, 2, 3] +const wat = { extra: "life" } +let a: A = { + x: [], + y, + z: { + ka: false + }, + ...wat +} +a.x.push('hi') +a.y.push(4) +let b = a.z.ka +b = a.z.ki // error, object is possibly undefined +a.extra.length // error, reference doesn't match 'wat', so object is possibly undefined -let f2: Foo = { bar: [] }; -f2.bar.push(0) From 53dac87bd009dc04dcb36462a9f39e25b0cd79fe Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 16 Feb 2018 09:52:06 -0800 Subject: [PATCH 05/10] Simplify initializer flow construction in binder --- src/compiler/binder.ts | 47 ++++++++++--------- src/compiler/checker.ts | 3 +- ...rolFlowObjectLiteralDeclaration.errors.txt | 3 +- .../controlFlowObjectLiteralDeclaration.js | 2 + ...ontrolFlowObjectLiteralDeclaration.symbols | 33 ++++++------- .../controlFlowObjectLiteralDeclaration.types | 1 + .../controlFlowObjectLiteralDeclaration.ts | 1 + 7 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 36dccc21fab8f..2e789c66cb4a0 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1230,49 +1230,53 @@ namespace ts { } } - function bindDestructuringTargetFlow(node: Expression, SPECIAL: boolean) { + function bindDestructuringTargetFlow(node: Expression) { if (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.EqualsToken) { - bindAssignmentTargetFlow((node).left, SPECIAL); + bindAssignmentTargetFlow((node).left); } else { - bindAssignmentTargetFlow(node, SPECIAL); + bindAssignmentTargetFlow(node); } } - function bindAssignmentTargetFlow(node: Expression, SPECIAL?: boolean) { + function bindInitializerFlow(node: Expression): void { if (isNarrowableReference(node)) { - if (SPECIAL) { - currentFlow = createFlowInitializer(currentFlow, node); - } - else { - currentFlow = createFlowAssignment(currentFlow, node); + currentFlow = createFlowInitializer(currentFlow, node); + } + else if (isObjectLiteralExpression(node)) { + for (const p of node.properties) { + if (p.name && p.name.kind === SyntaxKind.Identifier) { + // TODO: support string, number, computed + bindInitializerFlow(p.name); + } } } + } + + function bindAssignmentTargetFlow(node: Expression) { + if (isNarrowableReference(node)) { + currentFlow = createFlowAssignment(currentFlow, node); + } else if (node.kind === SyntaxKind.ArrayLiteralExpression) { for (const e of (node).elements) { if (e.kind === SyntaxKind.SpreadElement) { - bindAssignmentTargetFlow((e).expression, SPECIAL); + bindAssignmentTargetFlow((e).expression); } else { - bindDestructuringTargetFlow(e, SPECIAL); + bindDestructuringTargetFlow(e); } } } else if (node.kind === SyntaxKind.ObjectLiteralExpression) { for (const p of (node).properties) { if (p.kind === SyntaxKind.PropertyAssignment) { - if (SPECIAL && p.name.kind === SyntaxKind.Identifier) { - bindAssignmentTargetFlow(p.name, SPECIAL); - } - else { - bindDestructuringTargetFlow(p.initializer, SPECIAL); - } + bindDestructuringTargetFlow(p.initializer); } else if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { - bindAssignmentTargetFlow(p.name, SPECIAL); + bindAssignmentTargetFlow(p.name); } else if (p.kind === SyntaxKind.SpreadAssignment) { - bindAssignmentTargetFlow(p.expression, SPECIAL); + bindAssignmentTargetFlow(p.expression); } } } @@ -1377,9 +1381,8 @@ namespace ts { node.type && node.initializer && isIdentifier(node.name) && - node.initializer.kind === SyntaxKind.ObjectLiteralExpression) { - // should be fine! - bindAssignmentTargetFlow(node.initializer as ObjectLiteralExpression, /*SPECIAL*/ true); + isObjectLiteralExpression(node.initializer)) { + bindInitializerFlow(node.initializer); } } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b8fd05ad99e18..834612d5b8d3f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12687,8 +12687,7 @@ namespace ts { function getTypeAtFlowInitializer(flow: FlowInitializer): Type { const node = flow.node; - // TODO: More conditions here to do with object literals and variable declarations - // (and actually recursive ones) + // TODO: Recursive conditions here to do with object literals and variable declarations // node identifier=bar // .parent propertyassignment=bar: [] // .parent objectliteral= { bar: [] } diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt index a979ead7f7000..b3d38118983d0 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(24,1): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(25,1): error TS2532: Object is possibly 'undefined'. ==== tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts (1 errors) ==== @@ -11,6 +11,7 @@ tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(24,1) } extra?: string } + // TODO: String literals and numeric literals and computed properties const y = [1, 2, 3] const wat = { extra: "life" } let a: A = { diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js index 61dcfb2d1ecd9..1a6189eb6a247 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js @@ -8,6 +8,7 @@ type A = { } extra?: string } +// TODO: String literals and numeric literals and computed properties const y = [1, 2, 3] const wat = { extra: "life" } let a: A = { @@ -37,6 +38,7 @@ var __assign = (this && this.__assign) || Object.assign || function(t) { } return t; }; +// TODO: String literals and numeric literals and computed properties var y = [1, 2, 3]; var wat = { extra: "life" }; var a = __assign({ x: [], y: y, z: { diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols index 53816496916b5..f87d736135caf 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols @@ -20,67 +20,68 @@ type A = { extra?: string >extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 6, 5)) } +// TODO: String literals and numeric literals and computed properties const y = [1, 2, 3] ->y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 9, 5)) +>y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 10, 5)) const wat = { extra: "life" } ->wat : Symbol(wat, Decl(controlFlowObjectLiteralDeclaration.ts, 10, 5)) ->extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 10, 13)) +>wat : Symbol(wat, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 5)) +>extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 13)) let a: A = { ->a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 3)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 3)) >A : Symbol(A, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 0)) x: [], ->x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 12)) +>x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 12)) y, ->y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 10)) +>y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 13, 10)) z: { ->z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 13, 6)) +>z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 14, 6)) ka: false ->ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 14, 8)) +>ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 15, 8)) }, ...wat ->wat : Symbol(wat, Decl(controlFlowObjectLiteralDeclaration.ts, 10, 5)) +>wat : Symbol(wat, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 5)) } a.x.push('hi') >a.x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) >a.x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 10)) ->a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 3)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 3)) >x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 10)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) a.y.push(4) >a.y.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) >a.y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 1, 16)) ->a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 3)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 3)) >y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 1, 16)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) let b = a.z.ka ->b : Symbol(b, Decl(controlFlowObjectLiteralDeclaration.ts, 21, 3)) +>b : Symbol(b, Decl(controlFlowObjectLiteralDeclaration.ts, 22, 3)) >a.z.ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 3, 9)) >a.z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) ->a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 3)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 3)) >z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) >ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 3, 9)) b = a.z.ki // error, object is possibly undefined ->b : Symbol(b, Decl(controlFlowObjectLiteralDeclaration.ts, 21, 3)) +>b : Symbol(b, Decl(controlFlowObjectLiteralDeclaration.ts, 22, 3)) >a.z.ki : Symbol(ki, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 20)) >a.z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) ->a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 3)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 3)) >z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) >ki : Symbol(ki, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 20)) a.extra.length // error, reference doesn't match 'wat', so object is possibly undefined >a.extra.length : Symbol(String.length, Decl(lib.d.ts, --, --)) >a.extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 6, 5)) ->a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 3)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 3)) >extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 6, 5)) >length : Symbol(String.length, Decl(lib.d.ts, --, --)) diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types index 06d0536319c3d..217f4ecdd79a1 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types @@ -20,6 +20,7 @@ type A = { extra?: string >extra : string | undefined } +// TODO: String literals and numeric literals and computed properties const y = [1, 2, 3] >y : number[] >[1, 2, 3] : number[] diff --git a/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts b/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts index d72af4b0ce5d5..69614d34a79cd 100644 --- a/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts +++ b/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts @@ -8,6 +8,7 @@ type A = { } extra?: string } +// TODO: String literals and numeric literals and computed properties const y = [1, 2, 3] const wat = { extra: "life" } let a: A = { From 8c6fc7bad2a7af03657632f9bf148721c2a067cc Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 16 Feb 2018 15:48:42 -0800 Subject: [PATCH 06/10] Narrow by recursive property assignments --- src/compiler/binder.ts | 11 +++++--- src/compiler/checker.ts | 27 +++++++++---------- ...rolFlowObjectLiteralDeclaration.errors.txt | 7 ++++- .../controlFlowObjectLiteralDeclaration.types | 8 +++--- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 2e789c66cb4a0..2b447204f741c 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1249,6 +1249,9 @@ namespace ts { // TODO: support string, number, computed bindInitializerFlow(p.name); } + if (p.kind === SyntaxKind.PropertyAssignment) { + bindInitializerFlow(p.initializer); + } } } } @@ -1257,8 +1260,8 @@ namespace ts { if (isNarrowableReference(node)) { currentFlow = createFlowAssignment(currentFlow, node); } - else if (node.kind === SyntaxKind.ArrayLiteralExpression) { - for (const e of (node).elements) { + else if (isArrayLiteralExpression(node)) { + for (const e of node.elements) { if (e.kind === SyntaxKind.SpreadElement) { bindAssignmentTargetFlow((e).expression); } @@ -1267,8 +1270,8 @@ namespace ts { } } } - else if (node.kind === SyntaxKind.ObjectLiteralExpression) { - for (const p of (node).properties) { + else if (isObjectLiteralExpression(node)) { + for (const p of node.properties) { if (p.kind === SyntaxKind.PropertyAssignment) { bindDestructuringTargetFlow(p.initializer); } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 834612d5b8d3f..d9b2df3c13fe6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12687,29 +12687,28 @@ namespace ts { function getTypeAtFlowInitializer(flow: FlowInitializer): Type { const node = flow.node; - // TODO: Recursive conditions here to do with object literals and variable declarations - // node identifier=bar - // .parent propertyassignment=bar: [] - // .parent objectliteral= { bar: [] } - // .parent variabledeclaration = f2: Foo = { bar: [] } - // .name identifier f2 - const referenceMatchesPropertyAssignment = isPropertyAccessExpression(reference) && - isIdentifier(node) && - (isShorthandPropertyAssignment(node.parent) || isPropertyAssignment(node.parent)) && - reference.name.escapedText === node.escapedText; - if (referenceMatchesPropertyAssignment && - isVariableDeclaration(node.parent.parent.parent) && - isMatchingReference((reference as PropertyAccessExpression).expression, node.parent.parent.parent)) { + if (isMatchingInitializerReference(reference, node.parent)) { if (declaredType.flags & TypeFlags.Union) { const sourceNode = isPropertyAssignment(node.parent) ? node.parent.initializer : (node.parent as ShorthandPropertyAssignment).name; return getAssignmentReducedType(declaredType as UnionType, getTypeOfNode(sourceNode)); } return declaredType; } - // initializer doesn't affect reference return undefined; } + function isMatchingInitializerReference(reference: Node, initializer: Node): boolean { + if (isIdentifier(reference)) { + return isVariableDeclaration(initializer) && getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(reference)) === getSymbolOfNode(initializer); + } + else if (isPropertyAccessExpression(reference)) { + return (isShorthandPropertyAssignment(initializer) || isPropertyAssignment(initializer)) && + isIdentifier(initializer.name) && reference.name.escapedText === initializer.name.escapedText && + isMatchingInitializerReference(reference.expression, initializer.parent.parent); + } + return false; + } + function getTypeAtFlowAssignment(flow: FlowAssignment) { const node = flow.node; // Assignments only narrow the computed type if the declared type is a union type. Thus, we diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt index b3d38118983d0..9646df0a0cd0a 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt @@ -1,7 +1,9 @@ +tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(24,1): error TS2322: Type 'boolean | undefined' is not assignable to type 'boolean'. + Type 'undefined' is not assignable to type 'boolean'. tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(25,1): error TS2532: Object is possibly 'undefined'. -==== tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts (1 errors) ==== +==== tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts (2 errors) ==== type A = { x?: string[] y?: number[] @@ -26,6 +28,9 @@ tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(25,1) a.y.push(4) let b = a.z.ka b = a.z.ki // error, object is possibly undefined + ~ +!!! error TS2322: Type 'boolean | undefined' is not assignable to type 'boolean'. +!!! error TS2322: Type 'undefined' is not assignable to type 'boolean'. a.extra.length // error, reference doesn't match 'wat', so object is possibly undefined ~~~~~~~ !!! error TS2532: Object is possibly 'undefined'. diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types index 217f4ecdd79a1..d206de15247e2 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types @@ -77,16 +77,16 @@ a.y.push(4) >4 : 4 let b = a.z.ka ->b : boolean | undefined ->a.z.ka : boolean | undefined +>b : boolean +>a.z.ka : false >a.z : { ka?: boolean | undefined; ki?: boolean | undefined; } >a : A >z : { ka?: boolean | undefined; ki?: boolean | undefined; } ->ka : boolean | undefined +>ka : false b = a.z.ki // error, object is possibly undefined >b = a.z.ki : boolean | undefined ->b : boolean | undefined +>b : boolean >a.z.ki : boolean | undefined >a.z : { ka?: boolean | undefined; ki?: boolean | undefined; } >a : A From bae79566aa8df4411fce698618a7ac446e1ec5e4 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 16 Feb 2018 15:51:04 -0800 Subject: [PATCH 07/10] Update baselines --- tests/baselines/reference/destructuringTypeGuardFlow.types | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/baselines/reference/destructuringTypeGuardFlow.types b/tests/baselines/reference/destructuringTypeGuardFlow.types index 702fd3d897bb2..c4102b783bbaa 100644 --- a/tests/baselines/reference/destructuringTypeGuardFlow.types +++ b/tests/baselines/reference/destructuringTypeGuardFlow.types @@ -37,15 +37,15 @@ const aFoo: foo = { bar: 3, baz: "b", nested: { a: 1, b: "y" } }; >"y" : "y" if (aFoo.bar && aFoo.nested.b) { ->aFoo.bar && aFoo.nested.b : string | 0 | null +>aFoo.bar && aFoo.nested.b : string | 0 >aFoo.bar : number >aFoo : foo >bar : number ->aFoo.nested.b : string | null +>aFoo.nested.b : string >aFoo.nested : { a: number; b: string | null; } >aFoo : foo >nested : { a: number; b: string | null; } ->b : string | null +>b : string const { bar, baz, nested: {a, b: text} } = aFoo; >bar : number From 828b2ae9c84ca4f8331ab11a648e1b1e486490b6 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 16 Feb 2018 15:58:30 -0800 Subject: [PATCH 08/10] Give up on string/number/computed properties They are accessed via element access, which doesn't participate in control flow for performance reasons. --- src/compiler/binder.ts | 1 - ...rolFlowObjectLiteralDeclaration.errors.txt | 26 ++++++-- .../controlFlowObjectLiteralDeclaration.js | 24 +++++-- ...ontrolFlowObjectLiteralDeclaration.symbols | 64 +++++++++++++------ .../controlFlowObjectLiteralDeclaration.types | 38 +++++++++-- .../controlFlowObjectLiteralDeclaration.ts | 14 +++- 6 files changed, 128 insertions(+), 39 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 2b447204f741c..a9ff93c3f0c2a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1246,7 +1246,6 @@ namespace ts { else if (isObjectLiteralExpression(node)) { for (const p of node.properties) { if (p.name && p.name.kind === SyntaxKind.Identifier) { - // TODO: support string, number, computed bindInitializerFlow(p.name); } if (p.kind === SyntaxKind.PropertyAssignment) { diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt index 9646df0a0cd0a..fffa1e50a2129 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt @@ -1,9 +1,11 @@ -tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(24,1): error TS2322: Type 'boolean | undefined' is not assignable to type 'boolean'. +tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(30,1): error TS2322: Type 'boolean | undefined' is not assignable to type 'boolean'. Type 'undefined' is not assignable to type 'boolean'. -tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(25,1): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(31,1): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(32,1): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(33,1): error TS2532: Object is possibly 'undefined'. -==== tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts (2 errors) ==== +==== tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts (4 errors) ==== type A = { x?: string[] y?: number[] @@ -12,8 +14,12 @@ tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(25,1) ki?: boolean } extra?: string + 0?: string + 'two words'?: string } - // TODO: String literals and numeric literals and computed properties + // Note: spread assignments, as well as strings, numbers and computed properties, + // are not supported because they are all accessed with element access, which doesn't + // participate in control flow right now because of performance reasons. const y = [1, 2, 3] const wat = { extra: "life" } let a: A = { @@ -22,7 +28,9 @@ tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(25,1) z: { ka: false }, - ...wat + ...wat, + 0: 'hi', + 'two words': 'ho' } a.x.push('hi') a.y.push(4) @@ -31,9 +39,15 @@ tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts(25,1) ~ !!! error TS2322: Type 'boolean | undefined' is not assignable to type 'boolean'. !!! error TS2322: Type 'undefined' is not assignable to type 'boolean'. - a.extra.length // error, reference doesn't match 'wat', so object is possibly undefined + a.extra.length // error, reference doesn't match the spread ~~~~~~~ !!! error TS2532: Object is possibly 'undefined'. + a[0].length // error, element access doesn't narrow + ~~~~ +!!! error TS2532: Object is possibly 'undefined'. + a['two words'].length + ~~~~~~~~~~~~~~ +!!! error TS2532: Object is possibly 'undefined'. \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js index 1a6189eb6a247..01532bb56f6a2 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js @@ -7,8 +7,12 @@ type A = { ki?: boolean } extra?: string + 0?: string + 'two words'?: string } -// TODO: String literals and numeric literals and computed properties +// Note: spread assignments, as well as strings, numbers and computed properties, +// are not supported because they are all accessed with element access, which doesn't +// participate in control flow right now because of performance reasons. const y = [1, 2, 3] const wat = { extra: "life" } let a: A = { @@ -17,13 +21,17 @@ let a: A = { z: { ka: false }, - ...wat + ...wat, + 0: 'hi', + 'two words': 'ho' } a.x.push('hi') a.y.push(4) let b = a.z.ka b = a.z.ki // error, object is possibly undefined -a.extra.length // error, reference doesn't match 'wat', so object is possibly undefined +a.extra.length // error, reference doesn't match the spread +a[0].length // error, element access doesn't narrow +a['two words'].length @@ -38,14 +46,18 @@ var __assign = (this && this.__assign) || Object.assign || function(t) { } return t; }; -// TODO: String literals and numeric literals and computed properties +// Note: spread assignments, as well as strings, numbers and computed properties, +// are not supported because they are all accessed with element access, which doesn't +// participate in control flow right now because of performance reasons. var y = [1, 2, 3]; var wat = { extra: "life" }; var a = __assign({ x: [], y: y, z: { ka: false - } }, wat); + } }, wat, { 0: 'hi', 'two words': 'ho' }); a.x.push('hi'); a.y.push(4); var b = a.z.ka; b = a.z.ki; // error, object is possibly undefined -a.extra.length; // error, reference doesn't match 'wat', so object is possibly undefined +a.extra.length; // error, reference doesn't match the spread +a[0].length; // error, element access doesn't narrow +a['two words'].length; diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols index f87d736135caf..757ecabd79326 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols @@ -19,71 +19,97 @@ type A = { } extra?: string >extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 6, 5)) + + 0?: string +>0 : Symbol(0, Decl(controlFlowObjectLiteralDeclaration.ts, 7, 18)) + + 'two words'?: string +>'two words' : Symbol('two words', Decl(controlFlowObjectLiteralDeclaration.ts, 8, 14)) } -// TODO: String literals and numeric literals and computed properties +// Note: spread assignments, as well as strings, numbers and computed properties, +// are not supported because they are all accessed with element access, which doesn't +// participate in control flow right now because of performance reasons. const y = [1, 2, 3] ->y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 10, 5)) +>y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 14, 5)) const wat = { extra: "life" } ->wat : Symbol(wat, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 5)) ->extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 13)) +>wat : Symbol(wat, Decl(controlFlowObjectLiteralDeclaration.ts, 15, 5)) +>extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 15, 13)) let a: A = { ->a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 3)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 16, 3)) >A : Symbol(A, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 0)) x: [], ->x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 12)) +>x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 16, 12)) y, ->y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 13, 10)) +>y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 17, 10)) z: { ->z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 14, 6)) +>z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 18, 6)) ka: false ->ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 15, 8)) +>ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 19, 8)) }, - ...wat ->wat : Symbol(wat, Decl(controlFlowObjectLiteralDeclaration.ts, 11, 5)) + ...wat, +>wat : Symbol(wat, Decl(controlFlowObjectLiteralDeclaration.ts, 15, 5)) + + 0: 'hi', +>0 : Symbol(0, Decl(controlFlowObjectLiteralDeclaration.ts, 22, 11)) + + 'two words': 'ho' +>'two words' : Symbol('two words', Decl(controlFlowObjectLiteralDeclaration.ts, 23, 12)) } a.x.push('hi') >a.x.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) >a.x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 10)) ->a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 3)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 16, 3)) >x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 10)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) a.y.push(4) >a.y.push : Symbol(Array.push, Decl(lib.d.ts, --, --)) >a.y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 1, 16)) ->a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 3)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 16, 3)) >y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 1, 16)) >push : Symbol(Array.push, Decl(lib.d.ts, --, --)) let b = a.z.ka ->b : Symbol(b, Decl(controlFlowObjectLiteralDeclaration.ts, 22, 3)) +>b : Symbol(b, Decl(controlFlowObjectLiteralDeclaration.ts, 28, 3)) >a.z.ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 3, 9)) >a.z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) ->a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 3)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 16, 3)) >z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) >ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 3, 9)) b = a.z.ki // error, object is possibly undefined ->b : Symbol(b, Decl(controlFlowObjectLiteralDeclaration.ts, 22, 3)) +>b : Symbol(b, Decl(controlFlowObjectLiteralDeclaration.ts, 28, 3)) >a.z.ki : Symbol(ki, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 20)) >a.z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) ->a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 3)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 16, 3)) >z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 2, 16)) >ki : Symbol(ki, Decl(controlFlowObjectLiteralDeclaration.ts, 4, 20)) -a.extra.length // error, reference doesn't match 'wat', so object is possibly undefined +a.extra.length // error, reference doesn't match the spread >a.extra.length : Symbol(String.length, Decl(lib.d.ts, --, --)) >a.extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 6, 5)) ->a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 12, 3)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 16, 3)) >extra : Symbol(extra, Decl(controlFlowObjectLiteralDeclaration.ts, 6, 5)) >length : Symbol(String.length, Decl(lib.d.ts, --, --)) +a[0].length // error, element access doesn't narrow +>a[0].length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 16, 3)) +>0 : Symbol(0, Decl(controlFlowObjectLiteralDeclaration.ts, 7, 18)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) + +a['two words'].length +>a['two words'].length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>a : Symbol(a, Decl(controlFlowObjectLiteralDeclaration.ts, 16, 3)) +>'two words' : Symbol('two words', Decl(controlFlowObjectLiteralDeclaration.ts, 8, 14)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) + diff --git a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types index d206de15247e2..5208a96f0e246 100644 --- a/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types @@ -19,8 +19,16 @@ type A = { } extra?: string >extra : string | undefined + + 0?: string +>0 : string | undefined + + 'two words'?: string +>'two words' : string | undefined } -// TODO: String literals and numeric literals and computed properties +// Note: spread assignments, as well as strings, numbers and computed properties, +// are not supported because they are all accessed with element access, which doesn't +// participate in control flow right now because of performance reasons. const y = [1, 2, 3] >y : number[] >[1, 2, 3] : number[] @@ -37,7 +45,7 @@ const wat = { extra: "life" } let a: A = { >a : A >A : A ->{ x: [], y, z: { ka: false }, ...wat} : { extra: string; x: never[]; y: number[]; z: { ka: false; }; } +>{ x: [], y, z: { ka: false }, ...wat, 0: 'hi', 'two words': 'ho'} : { 0: string; 'two words': string; extra: string; x: never[]; y: number[]; z: { ka: false; }; } x: [], >x : never[] @@ -55,8 +63,16 @@ let a: A = { >false : false }, - ...wat + ...wat, >wat : { extra: string; } + + 0: 'hi', +>0 : string +>'hi' : "hi" + + 'two words': 'ho' +>'two words' : string +>'ho' : "ho" } a.x.push('hi') >a.x.push('hi') : number @@ -93,12 +109,26 @@ b = a.z.ki // error, object is possibly undefined >z : { ka?: boolean | undefined; ki?: boolean | undefined; } >ki : boolean | undefined -a.extra.length // error, reference doesn't match 'wat', so object is possibly undefined +a.extra.length // error, reference doesn't match the spread >a.extra.length : number >a.extra : string | undefined >a : A >extra : string | undefined >length : number +a[0].length // error, element access doesn't narrow +>a[0].length : number +>a[0] : string | undefined +>a : A +>0 : 0 +>length : number + +a['two words'].length +>a['two words'].length : number +>a['two words'] : string | undefined +>a : A +>'two words' : "two words" +>length : number + diff --git a/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts b/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts index 69614d34a79cd..0c206a5b24752 100644 --- a/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts +++ b/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts @@ -7,8 +7,12 @@ type A = { ki?: boolean } extra?: string + 0?: string + 'two words'?: string } -// TODO: String literals and numeric literals and computed properties +// Note: spread assignments, as well as strings, numbers and computed properties, +// are not supported because they are all accessed with element access, which doesn't +// participate in control flow right now because of performance reasons. const y = [1, 2, 3] const wat = { extra: "life" } let a: A = { @@ -17,12 +21,16 @@ let a: A = { z: { ka: false }, - ...wat + ...wat, + 0: 'hi', + 'two words': 'ho' } a.x.push('hi') a.y.push(4) let b = a.z.ka b = a.z.ki // error, object is possibly undefined -a.extra.length // error, reference doesn't match 'wat', so object is possibly undefined +a.extra.length // error, reference doesn't match the spread +a[0].length // error, element access doesn't narrow +a['two words'].length From 0e43839abe95b508fd64ea48b68698e527d02f06 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 16 Feb 2018 16:00:11 -0800 Subject: [PATCH 09/10] Undo gratuitous change --- src/compiler/checker.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d9b2df3c13fe6..7f9cfc16861c0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12737,6 +12737,7 @@ namespace ts { if (containsMatchingReference(reference, node)) { return declaredType; } + // Assignment doesn't affect reference return undefined; } From 66fd578069a15726d390cf2b9a7b0e5954da4bb1 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 16 Feb 2018 16:05:29 -0800 Subject: [PATCH 10/10] Clean up FlowInitializer type --- src/compiler/types.ts | 6 ++++-- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 13def6c2fc1ae..f11e2e7db2645 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2428,9 +2428,11 @@ namespace ts { antecedent: FlowNode; } - // a huge clone of FlowAssignment. Probably not needed! + // FlowInitializer represents the name of a property assignment in an object literal + // that initializes a variable that has a declared type. The property assignment + // narrows the respective member of the declared type. export interface FlowInitializer extends FlowNodeBase { - node: Expression | VariableDeclaration | BindingElement; + node: Identifier; antecedent: FlowNode; } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index b05662b635e5f..d37fbc85cc132 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1578,7 +1578,7 @@ declare namespace ts { antecedent: FlowNode; } interface FlowInitializer extends FlowNodeBase { - node: Expression | VariableDeclaration | BindingElement; + node: Identifier; antecedent: FlowNode; } interface FlowCondition extends FlowNodeBase { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 870810485e829..b0c3efe797a44 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1578,7 +1578,7 @@ declare namespace ts { antecedent: FlowNode; } interface FlowInitializer extends FlowNodeBase { - node: Expression | VariableDeclaration | BindingElement; + node: Identifier; antecedent: FlowNode; } interface FlowCondition extends FlowNodeBase {