From 402f1f6bf8af963d4cf127b6c123b5b1be1a50bf Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Oct 2016 17:45:15 -0700 Subject: [PATCH] Narrowed consts flow through object literal or class expression method Fixes #10501 --- src/compiler/binder.ts | 14 ++++- src/compiler/checker.ts | 4 +- src/compiler/types.ts | 2 +- src/compiler/utilities.ts | 6 ++ .../narrowedConstInMethod.errors.txt | 27 --------- .../reference/narrowedConstInMethod.symbols | 41 ++++++++++++++ .../reference/narrowedConstInMethod.types | 55 +++++++++++++++++++ 7 files changed, 117 insertions(+), 32 deletions(-) delete mode 100644 tests/baselines/reference/narrowedConstInMethod.errors.txt create mode 100644 tests/baselines/reference/narrowedConstInMethod.symbols create mode 100644 tests/baselines/reference/narrowedConstInMethod.types diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 27803b43526..9906bfdc661 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -84,6 +84,7 @@ namespace ts { IsFunctionExpression = 1 << 4, HasLocals = 1 << 5, IsInterface = 1 << 6, + IsObjectLiteralOrClassExpressionMethod = 1 << 7, } const binder = createBinder(); @@ -486,8 +487,8 @@ namespace ts { } else { currentFlow = { flags: FlowFlags.Start }; - if (containerFlags & ContainerFlags.IsFunctionExpression) { - (currentFlow).container = node; + if (containerFlags & (ContainerFlags.IsFunctionExpression | ContainerFlags.IsObjectLiteralOrClassExpressionMethod)) { + (currentFlow).container = node; } currentReturnTarget = undefined; } @@ -1237,9 +1238,12 @@ namespace ts { case SyntaxKind.SourceFile: return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals; + case SyntaxKind.MethodDeclaration: + if (isObjectLiteralOrClassExpressionMethod(node)) { + return ContainerFlags.IsContainer | ContainerFlags.IsControlFlowContainer | ContainerFlags.HasLocals | ContainerFlags.IsFunctionLike | ContainerFlags.IsObjectLiteralOrClassExpressionMethod; + } case SyntaxKind.Constructor: case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: @@ -2238,6 +2242,10 @@ namespace ts { } } + if (currentFlow && isObjectLiteralOrClassExpressionMethod(node)) { + node.flowNode = currentFlow; + } + return hasDynamicName(node) ? bindAnonymousDeclaration(node, symbolFlags, "__computed") : declareSymbolAndAddToSymbolTable(node, symbolFlags, symbolExcludes); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dfe6317e250..20e20e0eea9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9163,7 +9163,9 @@ namespace ts { // a const variable or parameter from an outer function, we extend the origin of the control flow // analysis to include the immediately enclosing function. while (flowContainer !== declarationContainer && - (flowContainer.kind === SyntaxKind.FunctionExpression || flowContainer.kind === SyntaxKind.ArrowFunction) && + (flowContainer.kind === SyntaxKind.FunctionExpression || + flowContainer.kind === SyntaxKind.ArrowFunction || + isObjectLiteralOrClassExpressionMethod(flowContainer)) && (isReadonlySymbol(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) { flowContainer = getControlFlowContainer(flowContainer); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 638504e613f..5c4ffedb33c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1922,7 +1922,7 @@ namespace ts { // function, the container property references the function (which in turn has a flowNode // property for the containing control flow). export interface FlowStart extends FlowNode { - container?: FunctionExpression | ArrowFunction; + container?: FunctionExpression | ArrowFunction | MethodDeclaration; } // FlowLabel represents a junction with multiple possible preceding control flows. diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 558015bb0e3..498c195ea3a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -895,6 +895,12 @@ namespace ts { return node && node.kind === SyntaxKind.MethodDeclaration && node.parent.kind === SyntaxKind.ObjectLiteralExpression; } + export function isObjectLiteralOrClassExpressionMethod(node: Node): node is MethodDeclaration { + return node.kind === SyntaxKind.MethodDeclaration && + (node.parent.kind === SyntaxKind.ObjectLiteralExpression || + node.parent.kind === SyntaxKind.ClassExpression); + } + export function isIdentifierTypePredicate(predicate: TypePredicate): predicate is IdentifierTypePredicate { return predicate && predicate.kind === TypePredicateKind.Identifier; } diff --git a/tests/baselines/reference/narrowedConstInMethod.errors.txt b/tests/baselines/reference/narrowedConstInMethod.errors.txt deleted file mode 100644 index ebb85218a5b..00000000000 --- a/tests/baselines/reference/narrowedConstInMethod.errors.txt +++ /dev/null @@ -1,27 +0,0 @@ -tests/cases/compiler/narrowedConstInMethod.ts(6,28): error TS2531: Object is possibly 'null'. -tests/cases/compiler/narrowedConstInMethod.ts(15,28): error TS2531: Object is possibly 'null'. - - -==== tests/cases/compiler/narrowedConstInMethod.ts (2 errors) ==== - - function f() { - const x: string | null = {}; - if (x !== null) { - return { - bar() { return x.length; } // Error: possibly null x - ~ -!!! error TS2531: Object is possibly 'null'. - }; - } - } - - function f2() { - const x: string | null = {}; - if (x !== null) { - return class { - bar() { return x.length; } // Error: possibly null x - ~ -!!! error TS2531: Object is possibly 'null'. - }; - } - } \ No newline at end of file diff --git a/tests/baselines/reference/narrowedConstInMethod.symbols b/tests/baselines/reference/narrowedConstInMethod.symbols new file mode 100644 index 00000000000..1fd05dcea5e --- /dev/null +++ b/tests/baselines/reference/narrowedConstInMethod.symbols @@ -0,0 +1,41 @@ +=== tests/cases/compiler/narrowedConstInMethod.ts === + +function f() { +>f : Symbol(f, Decl(narrowedConstInMethod.ts, 0, 0)) + + const x: string | null = {}; +>x : Symbol(x, Decl(narrowedConstInMethod.ts, 2, 9)) + + if (x !== null) { +>x : Symbol(x, Decl(narrowedConstInMethod.ts, 2, 9)) + + return { + bar() { return x.length; } // Error: possibly null x +>bar : Symbol(bar, Decl(narrowedConstInMethod.ts, 4, 16)) +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(narrowedConstInMethod.ts, 2, 9)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) + + }; + } +} + +function f2() { +>f2 : Symbol(f2, Decl(narrowedConstInMethod.ts, 8, 1)) + + const x: string | null = {}; +>x : Symbol(x, Decl(narrowedConstInMethod.ts, 11, 9)) + + if (x !== null) { +>x : Symbol(x, Decl(narrowedConstInMethod.ts, 11, 9)) + + return class { + bar() { return x.length; } // Error: possibly null x +>bar : Symbol((Anonymous class).bar, Decl(narrowedConstInMethod.ts, 13, 22)) +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(narrowedConstInMethod.ts, 11, 9)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) + + }; + } +} diff --git a/tests/baselines/reference/narrowedConstInMethod.types b/tests/baselines/reference/narrowedConstInMethod.types new file mode 100644 index 00000000000..a73f2edfccb --- /dev/null +++ b/tests/baselines/reference/narrowedConstInMethod.types @@ -0,0 +1,55 @@ +=== tests/cases/compiler/narrowedConstInMethod.ts === + +function f() { +>f : () => { bar(): number; } | undefined + + const x: string | null = {}; +>x : string | null +>null : null +>{} : any +>{} : {} + + if (x !== null) { +>x !== null : boolean +>x : string | null +>null : null + + return { +>{ bar() { return x.length; } // Error: possibly null x } : { bar(): number; } + + bar() { return x.length; } // Error: possibly null x +>bar : () => number +>x.length : number +>x : string +>length : number + + }; + } +} + +function f2() { +>f2 : () => typeof (Anonymous class) | undefined + + const x: string | null = {}; +>x : string | null +>null : null +>{} : any +>{} : {} + + if (x !== null) { +>x !== null : boolean +>x : string | null +>null : null + + return class { +>class { bar() { return x.length; } // Error: possibly null x } : typeof (Anonymous class) + + bar() { return x.length; } // Error: possibly null x +>bar : () => number +>x.length : number +>x : string +>length : number + + }; + } +}