diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 05edda7cd3c52..a9ff93c3f0c2a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -859,6 +859,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 }; @@ -1234,12 +1239,28 @@ namespace ts { } } + function bindInitializerFlow(node: Expression): void { + if (isNarrowableReference(node)) { + currentFlow = createFlowInitializer(currentFlow, node); + } + else if (isObjectLiteralExpression(node)) { + for (const p of node.properties) { + if (p.name && p.name.kind === SyntaxKind.Identifier) { + bindInitializerFlow(p.name); + } + if (p.kind === SyntaxKind.PropertyAssignment) { + bindInitializerFlow(p.initializer); + } + } + } + } + function bindAssignmentTargetFlow(node: Expression) { 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); } @@ -1248,16 +1269,16 @@ 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); + bindDestructuringTargetFlow(p.initializer); } else if (p.kind === SyntaxKind.ShorthandPropertyAssignment) { - bindAssignmentTargetFlow((p).name); + bindAssignmentTargetFlow(p.name); } else if (p.kind === SyntaxKind.SpreadAssignment) { - bindAssignmentTargetFlow((p).expression); + bindAssignmentTargetFlow(p.expression); } } } @@ -1358,6 +1379,13 @@ namespace ts { } else { currentFlow = createFlowAssignment(currentFlow, node); + if (isVariableDeclaration(node) && + node.type && + node.initializer && + isIdentifier(node.name) && + isObjectLiteralExpression(node.initializer)) { + bindInitializerFlow(node.initializer); + } } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2df80926c1467..7f9cfc16861c0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12630,6 +12630,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); } @@ -12678,6 +12685,30 @@ namespace ts { } } + function getTypeAtFlowInitializer(flow: FlowInitializer): Type { + const node = flow.node; + 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; + } + 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/src/compiler/types.ts b/src/compiler/types.ts index 0e3f83f6e09c4..f11e2e7db2645 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2384,6 +2384,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 } @@ -2427,6 +2428,14 @@ namespace ts { antecedent: FlowNode; } + // 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: Identifier; + 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 { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 370d841a7f19f..d37fbc85cc132 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1548,6 +1548,7 @@ declare namespace ts { Shared = 1024, PreFinally = 2048, AfterFinally = 4096, + Initializer = 8192, Label = 12, Condition = 96, } @@ -1576,6 +1577,10 @@ declare namespace ts { node: Expression | VariableDeclaration | BindingElement; antecedent: FlowNode; } + interface FlowInitializer extends FlowNodeBase { + node: Identifier; + 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 baed457b3b055..b0c3efe797a44 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1548,6 +1548,7 @@ declare namespace ts { Shared = 1024, PreFinally = 2048, AfterFinally = 4096, + Initializer = 8192, Label = 12, Condition = 96, } @@ -1576,6 +1577,10 @@ declare namespace ts { node: Expression | VariableDeclaration | BindingElement; antecedent: FlowNode; } + interface FlowInitializer extends FlowNodeBase { + node: Identifier; + antecedent: FlowNode; + } interface FlowCondition extends FlowNodeBase { expression: Expression; antecedent: FlowNode; 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.errors.txt b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt new file mode 100644 index 0000000000000..fffa1e50a2129 --- /dev/null +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.errors.txt @@ -0,0 +1,53 @@ +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(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 (4 errors) ==== + type A = { + x?: string[] + y?: number[] + z?: { + ka?: boolean + ki?: boolean + } + extra?: string + 0?: string + 'two words'?: string + } + // 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 = { + x: [], + y, + z: { + ka: false + }, + ...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 + ~ +!!! 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 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 new file mode 100644 index 0000000000000..01532bb56f6a2 --- /dev/null +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.js @@ -0,0 +1,63 @@ +//// [controlFlowObjectLiteralDeclaration.ts] +type A = { + x?: string[] + y?: number[] + z?: { + ka?: boolean + ki?: boolean + } + extra?: string + 0?: string + 'two words'?: string +} +// 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 = { + x: [], + y, + z: { + ka: false + }, + ...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 the spread +a[0].length // error, element access doesn't narrow +a['two words'].length + + + + +//// [controlFlowObjectLiteralDeclaration.js] +"use strict"; +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; +}; +// 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, { 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 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 new file mode 100644 index 0000000000000..757ecabd79326 --- /dev/null +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.symbols @@ -0,0 +1,115 @@ +=== tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts === +type A = { +>A : Symbol(A, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 0)) + + 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)) + + 0?: string +>0 : Symbol(0, Decl(controlFlowObjectLiteralDeclaration.ts, 7, 18)) + + 'two words'?: string +>'two words' : Symbol('two words', Decl(controlFlowObjectLiteralDeclaration.ts, 8, 14)) +} +// 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, 14, 5)) + +const wat = { extra: "life" } +>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, 16, 3)) +>A : Symbol(A, Decl(controlFlowObjectLiteralDeclaration.ts, 0, 0)) + + x: [], +>x : Symbol(x, Decl(controlFlowObjectLiteralDeclaration.ts, 16, 12)) + + y, +>y : Symbol(y, Decl(controlFlowObjectLiteralDeclaration.ts, 17, 10)) + + z: { +>z : Symbol(z, Decl(controlFlowObjectLiteralDeclaration.ts, 18, 6)) + + ka: false +>ka : Symbol(ka, Decl(controlFlowObjectLiteralDeclaration.ts, 19, 8)) + + }, + ...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, 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, 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, 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, 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, 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, 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 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, 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 new file mode 100644 index 0000000000000..5208a96f0e246 --- /dev/null +++ b/tests/baselines/reference/controlFlowObjectLiteralDeclaration.types @@ -0,0 +1,134 @@ +=== tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts === +type A = { +>A : A + + 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 + + 0?: string +>0 : string | undefined + + 'two words'?: string +>'two words' : string | undefined +} +// 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[] +>1 : 1 +>2 : 2 +>3 : 3 + +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, 0: 'hi', 'two words': 'ho'} : { 0: string; 'two words': string; extra: string; x: never[]; y: number[]; z: { ka: false; }; } + + x: [], +>x : never[] +>[] : never[] + + y, +>y : number[] + + z: { +>z : { ka: false; } +>{ ka: false } : { ka: false; } + + ka: false +>ka : false +>false : false + + }, + ...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 +>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 +>4 : 4 + +let b = a.z.ka +>b : boolean +>a.z.ka : false +>a.z : { ka?: boolean | undefined; ki?: boolean | undefined; } +>a : A +>z : { ka?: boolean | undefined; ki?: boolean | undefined; } +>ka : false + +b = a.z.ki // error, object is possibly undefined +>b = a.z.ki : boolean | undefined +>b : boolean +>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 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/baselines/reference/destructuringTypeGuardFlow.types b/tests/baselines/reference/destructuringTypeGuardFlow.types index e04dfa645c9ca..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 : number | null +>aFoo.bar && aFoo.nested.b : string | 0 +>aFoo.bar : number >aFoo : foo ->bar : number | null ->aFoo.nested.b : string | null +>bar : number +>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 diff --git a/tests/baselines/reference/typeGuardsOnClassProperty.types b/tests/baselines/reference/typeGuardsOnClassProperty.types index 510fac963d0e8..1a04f022fd93c 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..0c206a5b24752 --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowObjectLiteralDeclaration.ts @@ -0,0 +1,36 @@ +// @strict: true +type A = { + x?: string[] + y?: number[] + z?: { + ka?: boolean + ki?: boolean + } + extra?: string + 0?: string + 'two words'?: string +} +// 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 = { + x: [], + y, + z: { + ka: false + }, + ...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 the spread +a[0].length // error, element access doesn't narrow +a['two words'].length + +