From d543028226e0e8442fd01153f4393b10f829c3b0 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 6 Oct 2016 13:27:18 -0700 Subject: [PATCH 1/2] Add test case for narrowed const in object literal and class expression method --- .../narrowedConstInMethod.errors.txt | 27 +++++++++++++ .../reference/narrowedConstInMethod.js | 40 +++++++++++++++++++ tests/cases/compiler/narrowedConstInMethod.ts | 19 +++++++++ 3 files changed, 86 insertions(+) create mode 100644 tests/baselines/reference/narrowedConstInMethod.errors.txt create mode 100644 tests/baselines/reference/narrowedConstInMethod.js create mode 100644 tests/cases/compiler/narrowedConstInMethod.ts diff --git a/tests/baselines/reference/narrowedConstInMethod.errors.txt b/tests/baselines/reference/narrowedConstInMethod.errors.txt new file mode 100644 index 0000000000000..ebb85218a5b93 --- /dev/null +++ b/tests/baselines/reference/narrowedConstInMethod.errors.txt @@ -0,0 +1,27 @@ +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.js b/tests/baselines/reference/narrowedConstInMethod.js new file mode 100644 index 0000000000000..f4cdc3def9184 --- /dev/null +++ b/tests/baselines/reference/narrowedConstInMethod.js @@ -0,0 +1,40 @@ +//// [narrowedConstInMethod.ts] + +function f() { + const x: string | null = {}; + if (x !== null) { + return { + bar() { return x.length; } // Error: possibly null x + }; + } +} + +function f2() { + const x: string | null = {}; + if (x !== null) { + return class { + bar() { return x.length; } // Error: possibly null x + }; + } +} + +//// [narrowedConstInMethod.js] +function f() { + var x = {}; + if (x !== null) { + return { + bar: function () { return x.length; } // Error: possibly null x + }; + } +} +function f2() { + var x = {}; + if (x !== null) { + return (function () { + function class_1() { + } + class_1.prototype.bar = function () { return x.length; }; // Error: possibly null x + return class_1; + }()); + } +} diff --git a/tests/cases/compiler/narrowedConstInMethod.ts b/tests/cases/compiler/narrowedConstInMethod.ts new file mode 100644 index 0000000000000..3ddf97a8df332 --- /dev/null +++ b/tests/cases/compiler/narrowedConstInMethod.ts @@ -0,0 +1,19 @@ +// @strictNullChecks: true + +function f() { + const x: string | null = {}; + if (x !== null) { + return { + bar() { return x.length; } // Error: possibly null x + }; + } +} + +function f2() { + const x: string | null = {}; + if (x !== null) { + return class { + bar() { return x.length; } // Error: possibly null x + }; + } +} \ No newline at end of file From 402f1f6bf8af963d4cf127b6c123b5b1be1a50bf Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 7 Oct 2016 17:45:15 -0700 Subject: [PATCH 2/2] 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 27803b43526ca..9906bfdc66125 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 dfe6317e25072..20e20e0eea9aa 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 638504e613f11..5c4ffedb33c9e 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 558015bb0e3ae..498c195ea3ab7 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 ebb85218a5b93..0000000000000 --- 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 0000000000000..1fd05dcea5ead --- /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 0000000000000..a73f2edfccbea --- /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 + + }; + } +}