From 995732fad27ed88d0b2d75333b72e53c0f8eb5e2 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 3 Nov 2016 15:35:10 -0700 Subject: [PATCH 01/29] Add undefined to default-valued parameters When --strictNullChecks is on --- src/compiler/checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 28b14502af..a459db40e1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3206,7 +3206,8 @@ namespace ts { // Use the type of the initializer expression if one is present if (declaration.initializer) { const type = checkDeclarationInitializer(declaration); - return addOptionality(type, /*optional*/ declaration.questionToken && includeOptionality); + const isOptional = declaration.questionToken || (declaration.initializer && declaration.kind === SyntaxKind.Parameter); + return addOptionality(type, isOptional && includeOptionality); } // If it is a short-hand property assignment, use the type of the identifier From 286845c6c5ea4b5bd791b23ed5d6f78291b01f16 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 3 Nov 2016 15:35:29 -0700 Subject: [PATCH 02/29] Test adding undefined to default-valued parameters When --strictNullChecks is on Also update a baseline that changes now. --- .../defaultParameterAddsUndefinedWithStrictNullChecks.js | 9 +++++++++ ...ultParameterAddsUndefinedWithStrictNullChecks.symbols | 7 +++++++ ...faultParameterAddsUndefinedWithStrictNullChecks.types | 8 ++++++++ tests/baselines/reference/optionalMethods.js | 4 ++-- tests/baselines/reference/optionalMethods.types | 6 +++--- .../defaultParameterAddsUndefinedWithStrictNullChecks.ts | 3 +++ 6 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js create mode 100644 tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols create mode 100644 tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types create mode 100644 tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js new file mode 100644 index 0000000000..34a540a290 --- /dev/null +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js @@ -0,0 +1,9 @@ +//// [defaultParameterAddsUndefinedWithStrictNullChecks.ts] +function f(addUndefined1 = "J", addUndefined2?: number) { +} + + +//// [defaultParameterAddsUndefinedWithStrictNullChecks.js] +function f(addUndefined1, addUndefined2) { + if (addUndefined1 === void 0) { addUndefined1 = "J"; } +} diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols new file mode 100644 index 0000000000..054291e602 --- /dev/null +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols @@ -0,0 +1,7 @@ +=== tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts === +function f(addUndefined1 = "J", addUndefined2?: number) { +>f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) +>addUndefined1 : Symbol(addUndefined1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 11)) +>addUndefined2 : Symbol(addUndefined2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 31)) +} + diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types new file mode 100644 index 0000000000..af35394da5 --- /dev/null +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types @@ -0,0 +1,8 @@ +=== tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts === +function f(addUndefined1 = "J", addUndefined2?: number) { +>f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => void +>addUndefined1 : string | undefined +>"J" : "J" +>addUndefined2 : number | undefined +} + diff --git a/tests/baselines/reference/optionalMethods.js b/tests/baselines/reference/optionalMethods.js index 28ed0bcd76..953ebb527a 100644 --- a/tests/baselines/reference/optionalMethods.js +++ b/tests/baselines/reference/optionalMethods.js @@ -128,11 +128,11 @@ interface Foo { declare function test1(x: Foo): void; declare class Bar { d: number; - e: number; + e: number | undefined; a: number; b?: number; c?: number | undefined; - constructor(d?: number, e?: number); + constructor(d?: number, e?: number | undefined); f(): number; g?(): number; h?(): number; diff --git a/tests/baselines/reference/optionalMethods.types b/tests/baselines/reference/optionalMethods.types index cf545eda50..cf86f8b6bc 100644 --- a/tests/baselines/reference/optionalMethods.types +++ b/tests/baselines/reference/optionalMethods.types @@ -87,7 +87,7 @@ class Bar { constructor(public d?: number, public e = 10) {} >d : number | undefined ->e : number +>e : number | undefined >10 : 10 f() { @@ -133,9 +133,9 @@ function test2(x: Bar) { >d : number | undefined x.e; ->x.e : number +>x.e : number | undefined >x : Bar ->e : number +>e : number | undefined x.f; >x.f : () => number diff --git a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts new file mode 100644 index 0000000000..b681c34afe --- /dev/null +++ b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts @@ -0,0 +1,3 @@ +// @strictNullChecks: true +function f(addUndefined1 = "J", addUndefined2?: number) { +} From 00ff0e5bb8952a3478c118e3833f2d93119dbc7c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 4 Nov 2016 08:22:55 -0700 Subject: [PATCH 03/29] Do not add undefined to parameter properties They are actually properties, so don't get undefined added when they have an initialiser. Also update baselines --- src/compiler/checker.ts | 4 +++- tests/baselines/reference/optionalMethods.js | 4 ++-- tests/baselines/reference/optionalMethods.types | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a459db40e1..814a003210 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3206,7 +3206,9 @@ namespace ts { // Use the type of the initializer expression if one is present if (declaration.initializer) { const type = checkDeclarationInitializer(declaration); - const isOptional = declaration.questionToken || (declaration.initializer && declaration.kind === SyntaxKind.Parameter); + // initialized parameters (but not parameter properties) are optional + const isOptional = declaration.questionToken || + (declaration.kind === SyntaxKind.Parameter && !(getModifierFlags(declaration) & ModifierFlags.ParameterPropertyModifier)); return addOptionality(type, isOptional && includeOptionality); } diff --git a/tests/baselines/reference/optionalMethods.js b/tests/baselines/reference/optionalMethods.js index 953ebb527a..28ed0bcd76 100644 --- a/tests/baselines/reference/optionalMethods.js +++ b/tests/baselines/reference/optionalMethods.js @@ -128,11 +128,11 @@ interface Foo { declare function test1(x: Foo): void; declare class Bar { d: number; - e: number | undefined; + e: number; a: number; b?: number; c?: number | undefined; - constructor(d?: number, e?: number | undefined); + constructor(d?: number, e?: number); f(): number; g?(): number; h?(): number; diff --git a/tests/baselines/reference/optionalMethods.types b/tests/baselines/reference/optionalMethods.types index cf86f8b6bc..cf545eda50 100644 --- a/tests/baselines/reference/optionalMethods.types +++ b/tests/baselines/reference/optionalMethods.types @@ -87,7 +87,7 @@ class Bar { constructor(public d?: number, public e = 10) {} >d : number | undefined ->e : number | undefined +>e : number >10 : 10 f() { @@ -133,9 +133,9 @@ function test2(x: Bar) { >d : number | undefined x.e; ->x.e : number | undefined +>x.e : number >x : Bar ->e : number | undefined +>e : number x.f; >x.f : () => number From 96c14de26a83eaac7cfdfeb1ed3b4f6734cf9e54 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 8 Dec 2016 10:25:21 -0800 Subject: [PATCH 04/29] More tests:default-valued parameter+strictNullChecks --- .../defaultParameterAddsUndefinedWithStrictNullChecks.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts index b681c34afe..17c9f06388 100644 --- a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts +++ b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts @@ -1,3 +1,9 @@ // @strictNullChecks: true function f(addUndefined1 = "J", addUndefined2?: number) { + return addUndefined1.length + (addUndefined2 || 0); } +function g(addUndefined = "J", addDefined: number) { + return addUndefined.length + addDefined; +} +let total = f() + f('a', 1) + f('b') + f(undefined, 2); +total = g('c', 3) + g(undefined, 4); From caad486a46c428e4099c2e8e5e9c92f538556f7f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 13 Dec 2016 11:10:12 -0800 Subject: [PATCH 05/29] More test cases and notes --- ...ameterAddsUndefinedWithStrictNullChecks.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts index 17c9f06388..eb11a85ac0 100644 --- a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts +++ b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts @@ -1,4 +1,5 @@ // @strictNullChecks: true +// @declaration: true function f(addUndefined1 = "J", addUndefined2?: number) { return addUndefined1.length + (addUndefined2 || 0); } @@ -7,3 +8,30 @@ function g(addUndefined = "J", addDefined: number) { } let total = f() + f('a', 1) + f('b') + f(undefined, 2); total = g('c', 3) + g(undefined, 4); + +function foo1(x: string = "string", b: number) { + x.length; +} + +function foo2(x: string | undefined = "string", b: number) { + x.length; // ok, should be narrowed to string +} + +function foo3(x = "string", b: number) { + x.length; // ok, should be narrowed to string +} + +function foo4(x: string | undefined, b: number) { + x.length; // error, Object is possibly 'undefined' +} + +foo1(undefined, 1); +foo2(undefined, 1); +foo3(undefined, 1); +foo4(undefined, 1); + + +// all four functions should have `x: string| undefined` in their type +// .d.ts should have `T | undefined` for all of them +// foo2 to have x be initialized on the first line +// need to remove special-case code to allow calling foo1(undefined) for x: string = "string" From bb6f3ad29a61895fb209f60012f2d8aaaff94d64 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 14 Dec 2016 08:48:39 -0800 Subject: [PATCH 06/29] Add parameter initialisers to control flow Also a little code to emit them in declarations, but this doesn't work yet. --- src/compiler/binder.ts | 5 ++++- src/compiler/checker.ts | 20 +++++++++++--------- src/compiler/declarationEmitter.ts | 8 ++++---- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index a6f5993f6c..4aa55d3dd7 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -810,7 +810,7 @@ namespace ts { }; } - function createFlowAssignment(antecedent: FlowNode, node: Expression | VariableDeclaration | BindingElement): FlowNode { + function createFlowAssignment(antecedent: FlowNode, node: Expression | VariableDeclaration | BindingElement | ParameterDeclaration): FlowNode { setFlowNodeReferenced(antecedent); return { flags: FlowFlags.Assignment, @@ -2311,6 +2311,9 @@ namespace ts { } else { declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); + if (node.initializer) { + currentFlow = createFlowAssignment(currentFlow, node); + } } // If this is a property-parameter, then also declare the property symbol into the diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 07fbb2a716..72438c244f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3245,7 +3245,9 @@ namespace ts { // Use type from type annotation if one is present if (declaration.type) { - return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality); + const isOptional = declaration.questionToken || + (declaration.initializer && declaration.kind === SyntaxKind.Parameter && !(getModifierFlags(declaration) & ModifierFlags.ParameterPropertyModifier)); + return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ isOptional && includeOptionality); } if ((compilerOptions.noImplicitAny || declaration.flags & NodeFlags.JavaScriptFile) && @@ -9042,8 +9044,8 @@ namespace ts { switch (source.kind) { case SyntaxKind.Identifier: return target.kind === SyntaxKind.Identifier && getResolvedSymbol(source) === getResolvedSymbol(target) || - (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && - getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source)) === getSymbolOfNode(target); + (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source)) === getSymbolOfNode(target) || + target.kind === SyntaxKind.Parameter && getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target); case SyntaxKind.ThisKeyword: return target.kind === SyntaxKind.ThisKeyword; case SyntaxKind.PropertyAccessExpression: @@ -9314,7 +9316,7 @@ namespace ts { return links.resolvedType || getTypeOfExpression(node); } - function getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { + function getInitialTypeOfVariableDeclaration(node: VariableDeclaration | ParameterDeclaration) { if (node.initializer) { return getTypeOfInitializer(node.initializer); } @@ -9327,15 +9329,15 @@ namespace ts { return unknownType; } - function getInitialType(node: VariableDeclaration | BindingElement) { - return node.kind === SyntaxKind.VariableDeclaration ? - getInitialTypeOfVariableDeclaration(node) : + function getInitialType(node: VariableDeclaration | BindingElement | ParameterDeclaration) { + return node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.Parameter ? + getInitialTypeOfVariableDeclaration(node) : getInitialTypeOfBindingElement(node); } function getInitialOrAssignedType(node: VariableDeclaration | BindingElement | Expression) { - return node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? - getInitialType(node) : + return node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement || node.kind === SyntaxKind.Parameter ? + getInitialType(node) : getAssignedType(node); } diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index cd98622e08..23122d0561 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -319,12 +319,12 @@ namespace ts { } } - function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, type: TypeNode, getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic) { + function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, type: TypeNode, getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic, addUndefined?: boolean) { writer.getSymbolAccessibilityDiagnostic = getSymbolAccessibilityDiagnostic; write(": "); if (type) { // Write the type - emitType(type); + emitType(type, addUndefined); } else { errorNameNode = declaration.name; @@ -384,7 +384,7 @@ namespace ts { emitType(type); } - function emitType(type: TypeNode | Identifier | QualifiedName) { + function emitType(type: TypeNode | Identifier | QualifiedName, addUndefined?: boolean) { switch (type.kind) { case SyntaxKind.AnyKeyword: case SyntaxKind.StringKeyword: @@ -1593,7 +1593,7 @@ namespace ts { emitTypeOfVariableDeclarationFromTypeLiteral(node); } else if (!hasModifier(node.parent, ModifierFlags.Private)) { - writeTypeOfDeclaration(node, node.type, getParameterDeclarationTypeVisibilityError); + writeTypeOfDeclaration(node, node.type, getParameterDeclarationTypeVisibilityError, !!node.initializer && !(getModifierFlags(node) & ModifierFlags.ParameterPropertyModifier)); } function getParameterDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { From f097eafd11f20b2f08d5285b8e193920880a0ba7 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 14 Dec 2016 08:49:21 -0800 Subject: [PATCH 07/29] More tests of parameter initialiser type --- ...rAddsUndefinedWithStrictNullChecks.symbols | 79 ++++++++++++ ...terAddsUndefinedWithStrictNullChecks.types | 114 +++++++++++++++++- ...ameterAddsUndefinedWithStrictNullChecks.ts | 7 -- 3 files changed, 192 insertions(+), 8 deletions(-) diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols index 054291e602..ae11720ed1 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols @@ -3,5 +3,84 @@ function f(addUndefined1 = "J", addUndefined2?: number) { >f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) >addUndefined1 : Symbol(addUndefined1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 11)) >addUndefined2 : Symbol(addUndefined2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 31)) + + return addUndefined1.length + (addUndefined2 || 0); +>addUndefined1.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>addUndefined1 : Symbol(addUndefined1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 11)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>addUndefined2 : Symbol(addUndefined2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 31)) +} +function g(addUndefined = "J", addDefined: number) { +>g : Symbol(g, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 2, 1)) +>addUndefined : Symbol(addUndefined, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 3, 11)) +>addDefined : Symbol(addDefined, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 3, 30)) + + return addUndefined.length + addDefined; +>addUndefined.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>addUndefined : Symbol(addUndefined, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 3, 11)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>addDefined : Symbol(addDefined, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 3, 30)) +} +let total = f() + f('a', 1) + f('b') + f(undefined, 2); +>total : Symbol(total, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 6, 3)) +>f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) +>f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) +>f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) +>f : Symbol(f, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 0, 0)) +>undefined : Symbol(undefined) + +total = g('c', 3) + g(undefined, 4); +>total : Symbol(total, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 6, 3)) +>g : Symbol(g, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 2, 1)) +>g : Symbol(g, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 2, 1)) +>undefined : Symbol(undefined) + +function foo1(x: string = "string", b: number) { +>foo1 : Symbol(foo1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 7, 36)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 9, 14)) +>b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 9, 35)) + + x.length; +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 9, 14)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) } +function foo2(x: string | undefined = "string", b: number) { +>foo2 : Symbol(foo2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 11, 1)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 14)) +>b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 47)) + + x.length; // ok, should be narrowed to string +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 14)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +} + +function foo3(x = "string", b: number) { +>foo3 : Symbol(foo3, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 15, 1)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 14)) +>b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 27)) + + x.length; // ok, should be narrowed to string +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 14)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +} + +foo1(undefined, 1); +>foo1 : Symbol(foo1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 7, 36)) +>undefined : Symbol(undefined) + +foo2(undefined, 1); +>foo2 : Symbol(foo2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 11, 1)) +>undefined : Symbol(undefined) + +foo3(undefined, 1); +>foo3 : Symbol(foo3, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 15, 1)) +>undefined : Symbol(undefined) + + +// .d.ts should have `T | undefined` for all of them +// need to remove special-case code to allow calling foo1(undefined) for x: string = "string" + diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types index af35394da5..29dac89268 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types @@ -1,8 +1,120 @@ === tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts === function f(addUndefined1 = "J", addUndefined2?: number) { ->f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => void +>f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => number >addUndefined1 : string | undefined >"J" : "J" >addUndefined2 : number | undefined + + return addUndefined1.length + (addUndefined2 || 0); +>addUndefined1.length + (addUndefined2 || 0) : number +>addUndefined1.length : number +>addUndefined1 : string +>length : number +>(addUndefined2 || 0) : number +>addUndefined2 || 0 : number +>addUndefined2 : number | undefined +>0 : 0 +} +function g(addUndefined = "J", addDefined: number) { +>g : (addUndefined: string | undefined, addDefined: number) => number +>addUndefined : string | undefined +>"J" : "J" +>addDefined : number + + return addUndefined.length + addDefined; +>addUndefined.length + addDefined : number +>addUndefined.length : number +>addUndefined : string +>length : number +>addDefined : number +} +let total = f() + f('a', 1) + f('b') + f(undefined, 2); +>total : number +>f() + f('a', 1) + f('b') + f(undefined, 2) : number +>f() + f('a', 1) + f('b') : number +>f() + f('a', 1) : number +>f() : number +>f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => number +>f('a', 1) : number +>f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => number +>'a' : "a" +>1 : 1 +>f('b') : number +>f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => number +>'b' : "b" +>f(undefined, 2) : number +>f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => number +>undefined : undefined +>2 : 2 + +total = g('c', 3) + g(undefined, 4); +>total = g('c', 3) + g(undefined, 4) : number +>total : number +>g('c', 3) + g(undefined, 4) : number +>g('c', 3) : number +>g : (addUndefined: string | undefined, addDefined: number) => number +>'c' : "c" +>3 : 3 +>g(undefined, 4) : number +>g : (addUndefined: string | undefined, addDefined: number) => number +>undefined : undefined +>4 : 4 + +function foo1(x: string = "string", b: number) { +>foo1 : (x: string | undefined, b: number) => void +>x : string | undefined +>"string" : "string" +>b : number + + x.length; +>x.length : number +>x : string +>length : number } +function foo2(x: string | undefined = "string", b: number) { +>foo2 : (x: string | undefined, b: number) => void +>x : string | undefined +>"string" : "string" +>b : number + + x.length; // ok, should be narrowed to string +>x.length : number +>x : string +>length : number +} + +function foo3(x = "string", b: number) { +>foo3 : (x: string | undefined, b: number) => void +>x : string | undefined +>"string" : "string" +>b : number + + x.length; // ok, should be narrowed to string +>x.length : number +>x : string +>length : number +} + +foo1(undefined, 1); +>foo1(undefined, 1) : void +>foo1 : (x: string | undefined, b: number) => void +>undefined : undefined +>1 : 1 + +foo2(undefined, 1); +>foo2(undefined, 1) : void +>foo2 : (x: string | undefined, b: number) => void +>undefined : undefined +>1 : 1 + +foo3(undefined, 1); +>foo3(undefined, 1) : void +>foo3 : (x: string | undefined, b: number) => void +>undefined : undefined +>1 : 1 + + +// .d.ts should have `T | undefined` for all of them +// need to remove special-case code to allow calling foo1(undefined) for x: string = "string" + diff --git a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts index eb11a85ac0..f84e85286f 100644 --- a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts +++ b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts @@ -21,17 +21,10 @@ function foo3(x = "string", b: number) { x.length; // ok, should be narrowed to string } -function foo4(x: string | undefined, b: number) { - x.length; // error, Object is possibly 'undefined' -} - foo1(undefined, 1); foo2(undefined, 1); foo3(undefined, 1); -foo4(undefined, 1); -// all four functions should have `x: string| undefined` in their type // .d.ts should have `T | undefined` for all of them -// foo2 to have x be initialized on the first line // need to remove special-case code to allow calling foo1(undefined) for x: string = "string" From 604631881cfe883fe32e62a780315ad8f73ea7d2 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 16 Dec 2016 13:38:29 -0800 Subject: [PATCH 08/29] Add `| undefined` in .d.ts for initialised params --- src/compiler/declarationEmitter.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 23122d0561..9c8cd96280 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -319,12 +319,12 @@ namespace ts { } } - function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, type: TypeNode, getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic, addUndefined?: boolean) { + function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, type: TypeNode, getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic) { writer.getSymbolAccessibilityDiagnostic = getSymbolAccessibilityDiagnostic; write(": "); if (type) { // Write the type - emitType(type, addUndefined); + emitType(type); } else { errorNameNode = declaration.name; @@ -384,7 +384,7 @@ namespace ts { emitType(type); } - function emitType(type: TypeNode | Identifier | QualifiedName, addUndefined?: boolean) { + function emitType(type: TypeNode | Identifier | QualifiedName) { switch (type.kind) { case SyntaxKind.AnyKeyword: case SyntaxKind.StringKeyword: @@ -1593,7 +1593,10 @@ namespace ts { emitTypeOfVariableDeclarationFromTypeLiteral(node); } else if (!hasModifier(node.parent, ModifierFlags.Private)) { - writeTypeOfDeclaration(node, node.type, getParameterDeclarationTypeVisibilityError, !!node.initializer && !(getModifierFlags(node) & ModifierFlags.ParameterPropertyModifier)); + // use the checker's type, not the declared type, for an initialized parameter (that isn't a parameter property) + const isInitializedParameter = node.initializer && !(getModifierFlags(node) & ModifierFlags.ParameterPropertyModifier); + const typeNode = isInitializedParameter ? undefined : node.type; + writeTypeOfDeclaration(node, typeNode, getParameterDeclarationTypeVisibilityError); } function getParameterDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { From 739c0838e0483e6762d95f2e1d5b83d12a764608 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 16 Dec 2016 13:40:16 -0800 Subject: [PATCH 09/29] More tests of initialised parameters adding undefined --- ...ameterAddsUndefinedWithStrictNullChecks.js | 59 +++++++++++++++++++ .../destructureOptionalParameter.types | 2 +- ...ameterAddsUndefinedWithStrictNullChecks.ts | 3 +- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js index 34a540a290..107260dbad 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js @@ -1,9 +1,68 @@ //// [defaultParameterAddsUndefinedWithStrictNullChecks.ts] function f(addUndefined1 = "J", addUndefined2?: number) { + return addUndefined1.length + (addUndefined2 || 0); } +function g(addUndefined = "J", addDefined: number) { + return addUndefined.length + addDefined; +} +let total = f() + f('a', 1) + f('b') + f(undefined, 2); +total = g('c', 3) + g(undefined, 4); + +function foo1(x: string = "string", b: number) { + x.length; +} + +function foo2(x: string | undefined = "string", b: number) { + x.length; // ok, should be narrowed to string +} + +function foo3(x = "string", b: number) { + x.length; // ok, should be narrowed to string +} + +foo1(undefined, 1); +foo2(undefined, 1); +foo3(undefined, 1); + + +// .d.ts should have `T | undefined` for all of them +// need to remove special-case code to allow calling foo1(undefined) for x: string = "string" //// [defaultParameterAddsUndefinedWithStrictNullChecks.js] function f(addUndefined1, addUndefined2) { if (addUndefined1 === void 0) { addUndefined1 = "J"; } + return addUndefined1.length + (addUndefined2 || 0); } +function g(addUndefined, addDefined) { + if (addUndefined === void 0) { addUndefined = "J"; } + return addUndefined.length + addDefined; +} +var total = f() + f('a', 1) + f('b') + f(undefined, 2); +total = g('c', 3) + g(undefined, 4); +function foo1(x, b) { + if (x === void 0) { x = "string"; } + x.length; +} +function foo2(x, b) { + if (x === void 0) { x = "string"; } + x.length; // ok, should be narrowed to string +} +function foo3(x, b) { + if (x === void 0) { x = "string"; } + x.length; // ok, should be narrowed to string +} +foo1(undefined, 1); +foo2(undefined, 1); +foo3(undefined, 1); +// .d.ts should have `T | undefined` for all of them +// need to remove special-case code to allow calling foo1(undefined) for x: string = "string" + + +//// [defaultParameterAddsUndefinedWithStrictNullChecks.d.ts] +declare function f(addUndefined1?: string | undefined, addUndefined2?: number): number; +declare function g(addUndefined: string | undefined, addDefined: number): number; +declare let total: number; +declare function foo1(x: string | undefined, b: number): void; +declare function foo2(x: string | undefined, b: number): void; +declare function foo3(x: string | undefined, b: number): void; diff --git a/tests/baselines/reference/destructureOptionalParameter.types b/tests/baselines/reference/destructureOptionalParameter.types index a72b03f593..c068e3b569 100644 --- a/tests/baselines/reference/destructureOptionalParameter.types +++ b/tests/baselines/reference/destructureOptionalParameter.types @@ -8,7 +8,7 @@ declare function f1({ a, b }?: { a: number, b: string }): void; >b : string function f2({ a, b }: { a: number, b: number } = { a: 0, b: 0 }) { ->f2 : ({a, b}?: { a: number; b: number; }) => void +>f2 : ({a, b}?: { a: number; b: number; } | undefined) => void >a : number >b : number >a : number diff --git a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts index f84e85286f..01eefa7dd1 100644 --- a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts +++ b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts @@ -26,5 +26,4 @@ foo2(undefined, 1); foo3(undefined, 1); -// .d.ts should have `T | undefined` for all of them -// need to remove special-case code to allow calling foo1(undefined) for x: string = "string" +// .d.ts should have `T | undefined` for foo1, foo2, foo3 From cee708df8907ec0cd953ef5cd9d138fc46d3f539 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 21 Dec 2016 10:50:38 -0800 Subject: [PATCH 10/29] Remove undefined when checking patterns in function declarations --- src/compiler/checker.ts | 9 ++++++++- src/compiler/declarationEmitter.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 208af45196..3b0329881f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3098,7 +3098,8 @@ namespace ts { /** Return the inferred type for a binding element */ function getTypeForBindingElement(declaration: BindingElement): Type { const pattern = declaration.parent; - const parentType = getTypeForBindingElementParent(pattern.parent); + let parentType = getTypeForBindingElementParent(pattern.parent); + // If parent has the unknown (error) type, then so does this binding element if (parentType === unknownType) { return unknownType; @@ -3112,6 +3113,12 @@ namespace ts { } return parentType; } + // In strict null checking mode, a default value of a binding pattern adds undefined, + // which should be removed to get the type of the elements + const func = getContainingFunction(declaration); + if (strictNullChecks && func && !func.body && getFalsyFlags(parentType) & TypeFlags.Undefined) { + parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined); + } let type: Type; if (pattern.kind === SyntaxKind.ObjectBindingPattern) { diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 9c8cd96280..af32a5b408 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -319,7 +319,7 @@ namespace ts { } } - function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, type: TypeNode, getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic) { + function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, type: TypeNode, getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic) { writer.getSymbolAccessibilityDiagnostic = getSymbolAccessibilityDiagnostic; write(": "); if (type) { From 61c742ad6a1cbf3f9e219502eee1a45629bbf378 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 21 Dec 2016 10:51:57 -0800 Subject: [PATCH 11/29] Update tests --- .../defaultParameterAddsUndefinedWithStrictNullChecks.js | 6 ++---- ...efaultParameterAddsUndefinedWithStrictNullChecks.symbols | 3 +-- .../defaultParameterAddsUndefinedWithStrictNullChecks.types | 3 +-- tests/baselines/reference/destructureOptionalParameter.js | 2 +- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js index 107260dbad..3e75890738 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js @@ -25,8 +25,7 @@ foo2(undefined, 1); foo3(undefined, 1); -// .d.ts should have `T | undefined` for all of them -// need to remove special-case code to allow calling foo1(undefined) for x: string = "string" +// .d.ts should have `T | undefined` for foo1, foo2, foo3 //// [defaultParameterAddsUndefinedWithStrictNullChecks.js] @@ -55,8 +54,7 @@ function foo3(x, b) { foo1(undefined, 1); foo2(undefined, 1); foo3(undefined, 1); -// .d.ts should have `T | undefined` for all of them -// need to remove special-case code to allow calling foo1(undefined) for x: string = "string" +// .d.ts should have `T | undefined` for foo1, foo2, foo3 //// [defaultParameterAddsUndefinedWithStrictNullChecks.d.ts] diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols index ae11720ed1..97cccafe10 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols @@ -81,6 +81,5 @@ foo3(undefined, 1); >undefined : Symbol(undefined) -// .d.ts should have `T | undefined` for all of them -// need to remove special-case code to allow calling foo1(undefined) for x: string = "string" +// .d.ts should have `T | undefined` for foo1, foo2, foo3 diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types index 29dac89268..fa41973233 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types @@ -115,6 +115,5 @@ foo3(undefined, 1); >1 : 1 -// .d.ts should have `T | undefined` for all of them -// need to remove special-case code to allow calling foo1(undefined) for x: string = "string" +// .d.ts should have `T | undefined` for foo1, foo2, foo3 diff --git a/tests/baselines/reference/destructureOptionalParameter.js b/tests/baselines/reference/destructureOptionalParameter.js index b937d58412..1ffe678b50 100644 --- a/tests/baselines/reference/destructureOptionalParameter.js +++ b/tests/baselines/reference/destructureOptionalParameter.js @@ -40,7 +40,7 @@ declare function f1({a, b}?: { declare function f2({a, b}?: { a: number; b: number; -}): void; +} | undefined): void; interface Type { t: void; } From 6543048097a69a589dee0a3aab9503e5b58c46f0 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 21 Dec 2016 14:57:39 -0800 Subject: [PATCH 12/29] Change narrowing rules for parameter initialisers They don't narrow the parameter type, except to remove undefined, and only if the initialiser type doesn't include undefined. --- src/compiler/binder.ts | 13 +++++++++++-- src/compiler/checker.ts | 34 +++++++++++++++++++++++++++++++--- src/compiler/types.ts | 18 ++++++++++++------ 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 4aa55d3dd7..60af7a1267 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -810,7 +810,7 @@ namespace ts { }; } - function createFlowAssignment(antecedent: FlowNode, node: Expression | VariableDeclaration | BindingElement | ParameterDeclaration): FlowNode { + function createFlowAssignment(antecedent: FlowNode, node: Expression | VariableDeclaration | BindingElement): FlowNode { setFlowNodeReferenced(antecedent); return { flags: FlowFlags.Assignment, @@ -819,6 +819,15 @@ namespace ts { }; } + function createFlowInitializedParameter(antecedent: FlowNode, node: ParameterDeclaration): FlowNode { + setFlowNodeReferenced(antecedent); + return { + flags: FlowFlags.InitializedParameter, + antecedent, + node + }; + } + function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode { setFlowNodeReferenced(antecedent); return { @@ -2312,7 +2321,7 @@ namespace ts { else { declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); if (node.initializer) { - currentFlow = createFlowAssignment(currentFlow, node); + currentFlow = createFlowInitializedParameter(currentFlow, node); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3b0329881f..550fab0267 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9060,8 +9060,8 @@ namespace ts { switch (source.kind) { case SyntaxKind.Identifier: return target.kind === SyntaxKind.Identifier && getResolvedSymbol(source) === getResolvedSymbol(target) || - (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source)) === getSymbolOfNode(target) || - target.kind === SyntaxKind.Parameter && getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target); + (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement || target.kind === SyntaxKind.Parameter) && + getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target); case SyntaxKind.ThisKeyword: return target.kind === SyntaxKind.ThisKeyword; case SyntaxKind.PropertyAccessExpression: @@ -9155,6 +9155,13 @@ namespace ts { return false; } + function getInitializedParameterReducedType(declaredType: UnionType, assignedType: Type) { + if (declaredType === assignedType || getFalsyFlags(assignedType) & TypeFlags.Undefined) { + return declaredType; + } + return getTypeWithFacts(declaredType, TypeFacts.NEUndefined); + } + // Remove those constituent types of declaredType to which no constituent type of assignedType is assignable. // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean, // we remove type string. @@ -9351,7 +9358,7 @@ namespace ts { getInitialTypeOfBindingElement(node); } - function getInitialOrAssignedType(node: VariableDeclaration | BindingElement | Expression) { + function getInitialOrAssignedType(node: VariableDeclaration | BindingElement | Expression | ParameterDeclaration) { return node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement || node.kind === SyntaxKind.Parameter ? getInitialType(node) : getAssignedType(node); @@ -9622,6 +9629,13 @@ namespace ts { continue; } } + else if (flow.flags & FlowFlags.InitializedParameter) { + type = getTypeAtFlowInitializedParameter(flow as FlowInitializedParameter); + if (!type) { + flow = (flow as FlowInitializedParameter).antecedent; + continue; + } + } else if (flow.flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); } @@ -9669,6 +9683,20 @@ namespace ts { } } + function getTypeAtFlowInitializedParameter(flow: FlowInitializedParameter) { + const node = flow.node; + // Parameter initializers don't really narrow the declared type except to remove undefined. + // If the initializer includes undefined in the type, it doesn't even remove undefined. + if (isMatchingReference(reference, node)) { + if (declaredType.flags & TypeFlags.Union) { + return getInitializedParameterReducedType(declaredType, getInitialOrAssignedType(node)); + } + return declaredType; + } + + 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 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1ee59149b2..2363b76cb4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2042,12 +2042,13 @@ namespace ts { BranchLabel = 1 << 2, // Non-looping junction LoopLabel = 1 << 3, // Looping junction Assignment = 1 << 4, // Assignment - TrueCondition = 1 << 5, // Condition known to be true - FalseCondition = 1 << 6, // Condition known to be false - SwitchClause = 1 << 7, // Switch statement clause - ArrayMutation = 1 << 8, // Potential array mutation - Referenced = 1 << 9, // Referenced as antecedent once - Shared = 1 << 10, // Referenced as antecedent more than once + InitializedParameter = 1 << 5, // Parameter with initializer + TrueCondition = 1 << 6, // Condition known to be true + FalseCondition = 1 << 7, // Condition known to be false + SwitchClause = 1 << 8, // Switch statement clause + ArrayMutation = 1 << 9, // Potential array mutation + Referenced = 1 << 10, // Referenced as antecedent once + Shared = 1 << 11, // Referenced as antecedent more than once Label = BranchLabel | LoopLabel, Condition = TrueCondition | FalseCondition } @@ -2097,6 +2098,11 @@ namespace ts { antecedent: FlowNode; } + export interface FlowInitializedParameter extends FlowNode { + node: ParameterDeclaration; + antecedent: FlowNode; + } + export type FlowType = Type | IncompleteType; // Incomplete types occur during control flow analysis of loops. An IncompleteType From 3b1309d53f21e7793b708e5fc74fcf997ee92d1c Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 21 Dec 2016 14:58:44 -0800 Subject: [PATCH 13/29] Test parameter initialisation narrowing rules --- ...ameterAddsUndefinedWithStrictNullChecks.js | 37 ++++++++++++++- ...rAddsUndefinedWithStrictNullChecks.symbols | 36 ++++++++++++++- ...terAddsUndefinedWithStrictNullChecks.types | 45 ++++++++++++++++++- ...ameterAddsUndefinedWithStrictNullChecks.ts | 17 ++++++- 4 files changed, 130 insertions(+), 5 deletions(-) diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js index 3e75890738..50779650c3 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js @@ -20,12 +20,27 @@ function foo3(x = "string", b: number) { x.length; // ok, should be narrowed to string } +// .d.ts should have `T | undefined` for foo1, foo2, foo3 foo1(undefined, 1); foo2(undefined, 1); foo3(undefined, 1); -// .d.ts should have `T | undefined` for foo1, foo2, foo3 +function removeUndefinedButNotFalse(x = true) { + if (x === false) { + return x; + } +} + +declare const cond: boolean; +function removeNothing(y = cond ? true : undefined) { + if (y !== undefined) { + if (y === false) { + return y; + } + } + return true; +} //// [defaultParameterAddsUndefinedWithStrictNullChecks.js] @@ -51,10 +66,25 @@ function foo3(x, b) { if (x === void 0) { x = "string"; } x.length; // ok, should be narrowed to string } +// .d.ts should have `T | undefined` for foo1, foo2, foo3 foo1(undefined, 1); foo2(undefined, 1); foo3(undefined, 1); -// .d.ts should have `T | undefined` for foo1, foo2, foo3 +function removeUndefinedButNotFalse(x) { + if (x === void 0) { x = true; } + if (x === false) { + return x; + } +} +function removeNothing(y) { + if (y === void 0) { y = cond ? true : undefined; } + if (y !== undefined) { + if (y === false) { + return y; + } + } + return true; +} //// [defaultParameterAddsUndefinedWithStrictNullChecks.d.ts] @@ -64,3 +94,6 @@ declare let total: number; declare function foo1(x: string | undefined, b: number): void; declare function foo2(x: string | undefined, b: number): void; declare function foo3(x: string | undefined, b: number): void; +declare function removeUndefinedButNotFalse(x?: boolean | undefined): false | undefined; +declare const cond: boolean; +declare function removeNothing(y?: boolean | undefined): boolean; diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols index 97cccafe10..f722ed3af2 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols @@ -68,6 +68,7 @@ function foo3(x = "string", b: number) { >length : Symbol(String.length, Decl(lib.d.ts, --, --)) } +// .d.ts should have `T | undefined` for foo1, foo2, foo3 foo1(undefined, 1); >foo1 : Symbol(foo1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 7, 36)) >undefined : Symbol(undefined) @@ -81,5 +82,38 @@ foo3(undefined, 1); >undefined : Symbol(undefined) -// .d.ts should have `T | undefined` for foo1, foo2, foo3 +function removeUndefinedButNotFalse(x = true) { +>removeUndefinedButNotFalse : Symbol(removeUndefinedButNotFalse, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 24, 19)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 27, 36)) + + if (x === false) { +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 27, 36)) + + return x; +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 27, 36)) + } +} + +declare const cond: boolean; +>cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 33, 13)) + +function removeNothing(y = cond ? true : undefined) { +>removeNothing : Symbol(removeNothing, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 33, 28)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 23)) +>cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 33, 13)) +>undefined : Symbol(undefined) + + if (y !== undefined) { +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 23)) +>undefined : Symbol(undefined) + + if (y === false) { +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 23)) + + return y; +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 23)) + } + } + return true; +} diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types index fa41973233..15535eb435 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types @@ -96,6 +96,7 @@ function foo3(x = "string", b: number) { >length : number } +// .d.ts should have `T | undefined` for foo1, foo2, foo3 foo1(undefined, 1); >foo1(undefined, 1) : void >foo1 : (x: string | undefined, b: number) => void @@ -115,5 +116,47 @@ foo3(undefined, 1); >1 : 1 -// .d.ts should have `T | undefined` for foo1, foo2, foo3 +function removeUndefinedButNotFalse(x = true) { +>removeUndefinedButNotFalse : (x?: boolean | undefined) => false | undefined +>x : boolean | undefined +>true : true + + if (x === false) { +>x === false : boolean +>x : boolean +>false : false + + return x; +>x : false + } +} + +declare const cond: boolean; +>cond : boolean + +function removeNothing(y = cond ? true : undefined) { +>removeNothing : (y?: boolean | undefined) => boolean +>y : boolean | undefined +>cond ? true : undefined : true | undefined +>cond : boolean +>true : true +>undefined : undefined + + if (y !== undefined) { +>y !== undefined : boolean +>y : boolean | undefined +>undefined : undefined + + if (y === false) { +>y === false : boolean +>y : boolean +>false : false + + return y; +>y : false + } + } + return true; +>true : true +} diff --git a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts index 01eefa7dd1..44f17c949e 100644 --- a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts +++ b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts @@ -21,9 +21,24 @@ function foo3(x = "string", b: number) { x.length; // ok, should be narrowed to string } +// .d.ts should have `T | undefined` for foo1, foo2, foo3 foo1(undefined, 1); foo2(undefined, 1); foo3(undefined, 1); -// .d.ts should have `T | undefined` for foo1, foo2, foo3 +function removeUndefinedButNotFalse(x = true) { + if (x === false) { + return x; + } +} + +declare const cond: boolean; +function removeNothing(y = cond ? true : undefined) { + if (y !== undefined) { + if (y === false) { + return y; + } + } + return true; +} From e0bf73f79ee7e1f89d1daec28db7ca60027b94ae Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 21 Dec 2016 15:31:17 -0800 Subject: [PATCH 14/29] Remove old add-undefined code in getTypeOfParameter --- src/compiler/checker.ts | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 550fab0267..eb4866083c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6964,8 +6964,8 @@ namespace ts { const sourceParams = source.parameters; const targetParams = target.parameters; for (let i = 0; i < checkCount; i++) { - const s = i < sourceMax ? getTypeOfParameter(sourceParams[i]) : getRestTypeOfSignature(source); - const t = i < targetMax ? getTypeOfParameter(targetParams[i]) : getRestTypeOfSignature(target); + const s = i < sourceMax ? getTypeOfSymbol(sourceParams[i]) : getRestTypeOfSignature(source); + const t = i < targetMax ? getTypeOfSymbol(targetParams[i]) : getRestTypeOfSignature(target); const related = compareTypes(s, t, /*reportErrors*/ false) || compareTypes(t, s, reportErrors); if (!related) { if (reportErrors) { @@ -8163,8 +8163,8 @@ namespace ts { const targetLen = target.parameters.length; for (let i = 0; i < targetLen; i++) { - const s = isRestParameterIndex(source, i) ? getRestTypeOfSignature(source) : getTypeOfParameter(source.parameters[i]); - const t = isRestParameterIndex(target, i) ? getRestTypeOfSignature(target) : getTypeOfParameter(target.parameters[i]); + const s = isRestParameterIndex(source, i) ? getRestTypeOfSignature(source) : getTypeOfSymbol(source.parameters[i]); + const t = isRestParameterIndex(target, i) ? getRestTypeOfSignature(target) : getTypeOfSymbol(target.parameters[i]); const related = compareTypes(s, t); if (!related) { return Ternary.False; @@ -13863,21 +13863,10 @@ namespace ts { return getNonNullableType(checkExpression(node.expression)); } - function getTypeOfParameter(symbol: Symbol) { - const type = getTypeOfSymbol(symbol); - if (strictNullChecks) { - const declaration = symbol.valueDeclaration; - if (declaration && (declaration).initializer) { - return includeFalsyTypes(type, TypeFlags.Undefined); - } - } - return type; - } - function getTypeAtPosition(signature: Signature, pos: number): Type { return signature.hasRestParameter ? - pos < signature.parameters.length - 1 ? getTypeOfParameter(signature.parameters[pos]) : getRestTypeOfSignature(signature) : - pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : anyType; + pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) : + pos < signature.parameters.length ? getTypeOfSymbol(signature.parameters[pos]) : anyType; } function assignContextualParameterTypes(signature: Signature, context: Signature, mapper: TypeMapper) { From 156d5a9043fc3163e8e5439e5a6967ad6773fd91 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 5 Jan 2017 11:15:01 -0800 Subject: [PATCH 15/29] Add `| undefined` to all optional properties too in declarationEmitter.ts --- src/compiler/declarationEmitter.ts | 8 +++++--- ...efaultParameterAddsUndefinedWithStrictNullChecks.js | 2 +- .../reference/destructureOptionalParameter.js | 10 +++++----- tests/baselines/reference/optionalMethods.js | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index af32a5b408..965cb2330b 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -1593,9 +1593,11 @@ namespace ts { emitTypeOfVariableDeclarationFromTypeLiteral(node); } else if (!hasModifier(node.parent, ModifierFlags.Private)) { - // use the checker's type, not the declared type, for an initialized parameter (that isn't a parameter property) - const isInitializedParameter = node.initializer && !(getModifierFlags(node) & ModifierFlags.ParameterPropertyModifier); - const typeNode = isInitializedParameter ? undefined : node.type; + // use the checker's type, not the declared type, + // for optional parameters and initialized ones that aren't a parameter property + const typeShouldAddUndefined = resolver.isOptionalParameter(node) || + node.initializer && !(getModifierFlags(node) & ModifierFlags.ParameterPropertyModifier); + const typeNode = typeShouldAddUndefined ? undefined : node.type; writeTypeOfDeclaration(node, typeNode, getParameterDeclarationTypeVisibilityError); } diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js index 50779650c3..561fa65a15 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js @@ -88,7 +88,7 @@ function removeNothing(y) { //// [defaultParameterAddsUndefinedWithStrictNullChecks.d.ts] -declare function f(addUndefined1?: string | undefined, addUndefined2?: number): number; +declare function f(addUndefined1?: string | undefined, addUndefined2?: number | undefined): number; declare function g(addUndefined: string | undefined, addDefined: number): number; declare let total: number; declare function foo1(x: string | undefined, b: number): void; diff --git a/tests/baselines/reference/destructureOptionalParameter.js b/tests/baselines/reference/destructureOptionalParameter.js index 1ffe678b50..f375c41dbe 100644 --- a/tests/baselines/reference/destructureOptionalParameter.js +++ b/tests/baselines/reference/destructureOptionalParameter.js @@ -36,7 +36,7 @@ function f2(_a) { declare function f1({a, b}?: { a: number; b: string; -}): void; +} | undefined): void; declare function f2({a, b}?: { a: number; b: number; @@ -49,11 +49,11 @@ interface QueryMetadata { } interface QueryMetadataFactory { (selector: Type | string, {descendants, read}?: { - descendants?: boolean; + descendants?: boolean | undefined; read?: any; - }): ParameterDecorator; + } | undefined): ParameterDecorator; new (selector: Type | string, {descendants, read}?: { - descendants?: boolean; + descendants?: boolean | undefined; read?: any; - }): QueryMetadata; + } | undefined): QueryMetadata; } diff --git a/tests/baselines/reference/optionalMethods.js b/tests/baselines/reference/optionalMethods.js index 28ed0bcd76..c6051c622f 100644 --- a/tests/baselines/reference/optionalMethods.js +++ b/tests/baselines/reference/optionalMethods.js @@ -132,7 +132,7 @@ declare class Bar { a: number; b?: number; c?: number | undefined; - constructor(d?: number, e?: number); + constructor(d?: number | undefined, e?: number); f(): number; g?(): number; h?(): number; From 01a9e4f9be4eeebb1eb45350f6e55557035d7e38 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 26 Jan 2017 12:01:27 -0800 Subject: [PATCH 16/29] isOptionalParameter says unused IIFE arguments are optional Related to adding undefined, though not strictly the same, this change adds '?' to unused IIFE parameters in quick info. --- src/compiler/checker.ts | 8 +++++++- tests/baselines/reference/contextuallyTypedIife.types | 8 ++++---- tests/cases/fourslash/quickInfoDisplayPartsIife.ts | 5 +++++ 3 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 tests/cases/fourslash/quickInfoDisplayPartsIife.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 22db0d3276..34d71a0ab6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5145,6 +5145,12 @@ namespace ts { Debug.assert(parameterIndex >= 0); return parameterIndex >= signature.minArgumentCount; } + const iife = getImmediatelyInvokedFunctionExpression(node.parent); + if (iife) { + return !node.type && + !node.dotDotDotToken && + indexOf((node.parent as SignatureDeclaration).parameters, node) >= iife.arguments.length; + } return false; } @@ -14026,7 +14032,7 @@ namespace ts { return getTypeOfSymbol(symbol); } } - + function getTypeAtPosition(signature: Signature, pos: number): Type { return signature.hasRestParameter ? pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) : diff --git a/tests/baselines/reference/contextuallyTypedIife.types b/tests/baselines/reference/contextuallyTypedIife.types index 15038e7dd2..676755fb34 100644 --- a/tests/baselines/reference/contextuallyTypedIife.types +++ b/tests/baselines/reference/contextuallyTypedIife.types @@ -253,8 +253,8 @@ let eleven = (o => o.a(11))({ a: function(n) { return n; } }); // missing arguments (function(x, undefined) { return x; })(42); >(function(x, undefined) { return x; })(42) : number ->(function(x, undefined) { return x; }) : (x: number, undefined: any) => number ->function(x, undefined) { return x; } : (x: number, undefined: any) => number +>(function(x, undefined) { return x; }) : (x: number, undefined?: any) => number +>function(x, undefined) { return x; } : (x: number, undefined?: any) => number >x : number >undefined : any >x : number @@ -262,8 +262,8 @@ let eleven = (o => o.a(11))({ a: function(n) { return n; } }); ((x, y, z) => 42)(); >((x, y, z) => 42)() : number ->((x, y, z) => 42) : (x: any, y: any, z: any) => number ->(x, y, z) => 42 : (x: any, y: any, z: any) => number +>((x, y, z) => 42) : (x?: any, y?: any, z?: any) => number +>(x, y, z) => 42 : (x?: any, y?: any, z?: any) => number >x : any >y : any >z : any diff --git a/tests/cases/fourslash/quickInfoDisplayPartsIife.ts b/tests/cases/fourslash/quickInfoDisplayPartsIife.ts new file mode 100644 index 0000000000..e4ce2bf96e --- /dev/null +++ b/tests/cases/fourslash/quickInfoDisplayPartsIife.ts @@ -0,0 +1,5 @@ +/// +// @strictNullChecks: true +////var iife = (function foo/*1*/(x, y) { return x })(12); + +verify.quickInfoAt('1', '(local function) foo(x: number, y?: undefined): number'); From 3c243dbe0c262ed18a19f1afb0caa302717e9b0d Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 27 Jan 2017 14:29:00 -0800 Subject: [PATCH 17/29] Remove control flow-based undefined addition Just add undefined when displaying the type. Don't actually add it to the type. --- src/compiler/binder.ts | 12 ------ src/compiler/checker.ts | 92 ++++++++++++++++++----------------------- src/compiler/types.ts | 18 +++----- 3 files changed, 46 insertions(+), 76 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d0acfaed61..04dc4011a1 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -822,15 +822,6 @@ namespace ts { }; } - function createFlowInitializedParameter(antecedent: FlowNode, node: ParameterDeclaration): FlowNode { - setFlowNodeReferenced(antecedent); - return { - flags: FlowFlags.InitializedParameter, - antecedent, - node - }; - } - function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode { setFlowNodeReferenced(antecedent); return { @@ -2328,9 +2319,6 @@ namespace ts { } else { declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes); - if (node.initializer) { - currentFlow = createFlowInitializedParameter(currentFlow, node); - } } // If this is a property-parameter, then also declare the property symbol into the diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 34d71a0ab6..a9c98d0234 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2694,7 +2694,11 @@ namespace ts { writePunctuation(writer, SyntaxKind.ColonToken); writeSpace(writer); - buildTypeDisplay(getTypeOfSymbol(p), writer, enclosingDeclaration, flags, symbolStack); + let type = getTypeOfSymbol(p); + if (strictNullChecks && parameterNode.initializer && !(getModifierFlags(parameterNode) & ModifierFlags.ParameterPropertyModifier)) { + type = includeFalsyTypes(type, TypeFlags.Undefined); + } + buildTypeDisplay(type, writer, enclosingDeclaration, flags, symbolStack); } function buildBindingPatternDisplay(bindingPattern: BindingPattern, writer: SymbolWriter, enclosingDeclaration?: Node, flags?: TypeFormatFlags, symbolStack?: Symbol[]) { @@ -3315,9 +3319,7 @@ namespace ts { // Use type from type annotation if one is present if (declaration.type) { - const isOptional = declaration.questionToken || - (declaration.initializer && declaration.kind === SyntaxKind.Parameter && !(getModifierFlags(declaration) & ModifierFlags.ParameterPropertyModifier)); - return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ isOptional && includeOptionality); + return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality); } if ((compilerOptions.noImplicitAny || declaration.flags & NodeFlags.JavaScriptFile) && @@ -3368,10 +3370,7 @@ namespace ts { // Use the type of the initializer expression if one is present if (declaration.initializer) { const type = checkDeclarationInitializer(declaration); - // initialized parameters (but not parameter properties) are optional - const isOptional = declaration.questionToken || - (declaration.kind === SyntaxKind.Parameter && !(getModifierFlags(declaration) & ModifierFlags.ParameterPropertyModifier)); - return addOptionality(type, isOptional && includeOptionality); + return addOptionality(type, /*optional*/ declaration.questionToken && includeOptionality); } // If it is a short-hand property assignment, use the type of the identifier @@ -7101,8 +7100,8 @@ namespace ts { const sourceParams = source.parameters; const targetParams = target.parameters; for (let i = 0; i < checkCount; i++) { - const s = i < sourceMax ? getTypeOfSymbol(sourceParams[i]) : getRestTypeOfSignature(source); - const t = i < targetMax ? getTypeOfSymbol(targetParams[i]) : getRestTypeOfSignature(target); + const s = i < sourceMax ? getTypeOfParameter(sourceParams[i]) : getRestTypeOfSignature(source); + const t = i < targetMax ? getTypeOfParameter(targetParams[i]) : getRestTypeOfSignature(target); const related = compareTypes(s, t, /*reportErrors*/ false) || compareTypes(t, s, reportErrors); if (!related) { if (reportErrors) { @@ -8299,8 +8298,8 @@ namespace ts { const targetLen = target.parameters.length; for (let i = 0; i < targetLen; i++) { - const s = isRestParameterIndex(source, i) ? getRestTypeOfSignature(source) : getTypeOfSymbol(source.parameters[i]); - const t = isRestParameterIndex(target, i) ? getRestTypeOfSignature(target) : getTypeOfSymbol(target.parameters[i]); + const s = isRestParameterIndex(source, i) ? getRestTypeOfSignature(source) : getTypeOfParameter(source.parameters[i]); + const t = isRestParameterIndex(target, i) ? getRestTypeOfSignature(target) : getTypeOfParameter(target.parameters[i]); const related = compareTypes(s, t); if (!related) { return Ternary.False; @@ -9196,8 +9195,8 @@ namespace ts { switch (source.kind) { case SyntaxKind.Identifier: return target.kind === SyntaxKind.Identifier && getResolvedSymbol(source) === getResolvedSymbol(target) || - (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement || target.kind === SyntaxKind.Parameter) && - getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target); + (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && + getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source)) === getSymbolOfNode(target); case SyntaxKind.ThisKeyword: return target.kind === SyntaxKind.ThisKeyword; case SyntaxKind.PropertyAccessExpression: @@ -9291,13 +9290,6 @@ namespace ts { return false; } - function getInitializedParameterReducedType(declaredType: UnionType, assignedType: Type) { - if (declaredType === assignedType || getFalsyFlags(assignedType) & TypeFlags.Undefined) { - return declaredType; - } - return getTypeWithFacts(declaredType, TypeFacts.NEUndefined); - } - // Remove those constituent types of declaredType to which no constituent type of assignedType is assignable. // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean, // we remove type string. @@ -9478,7 +9470,7 @@ namespace ts { return links.resolvedType || getTypeOfExpression(node); } - function getInitialTypeOfVariableDeclaration(node: VariableDeclaration | ParameterDeclaration) { + function getInitialTypeOfVariableDeclaration(node: VariableDeclaration) { if (node.initializer) { return getTypeOfInitializer(node.initializer); } @@ -9491,15 +9483,15 @@ namespace ts { return unknownType; } - function getInitialType(node: VariableDeclaration | BindingElement | ParameterDeclaration) { - return node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.Parameter ? - getInitialTypeOfVariableDeclaration(node) : + function getInitialType(node: VariableDeclaration | BindingElement) { + return node.kind === SyntaxKind.VariableDeclaration ? + getInitialTypeOfVariableDeclaration(node) : getInitialTypeOfBindingElement(node); } - function getInitialOrAssignedType(node: VariableDeclaration | BindingElement | Expression | ParameterDeclaration) { - return node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement || node.kind === SyntaxKind.Parameter ? - getInitialType(node) : + function getInitialOrAssignedType(node: VariableDeclaration | BindingElement | Expression) { + return node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? + getInitialType(node) : getAssignedType(node); } @@ -9768,13 +9760,6 @@ namespace ts { continue; } } - else if (flow.flags & FlowFlags.InitializedParameter) { - type = getTypeAtFlowInitializedParameter(flow as FlowInitializedParameter); - if (!type) { - flow = (flow as FlowInitializedParameter).antecedent; - continue; - } - } else if (flow.flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); } @@ -9822,20 +9807,6 @@ namespace ts { } } - function getTypeAtFlowInitializedParameter(flow: FlowInitializedParameter) { - const node = flow.node; - // Parameter initializers don't really narrow the declared type except to remove undefined. - // If the initializer includes undefined in the type, it doesn't even remove undefined. - if (isMatchingReference(reference, node)) { - if (declaredType.flags & TypeFlags.Union) { - return getInitializedParameterReducedType(declaredType, getInitialOrAssignedType(node)); - } - return declaredType; - } - - 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 @@ -14033,10 +14004,21 @@ namespace ts { } } + function getTypeOfParameter(symbol: Symbol) { + const type = getTypeOfSymbol(symbol); + if (strictNullChecks) { + const declaration = symbol.valueDeclaration; + if (declaration && (declaration).initializer) { + return includeFalsyTypes(type, TypeFlags.Undefined); + } + } + return type; + } + function getTypeAtPosition(signature: Signature, pos: number): Type { return signature.hasRestParameter ? - pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) : - pos < signature.parameters.length ? getTypeOfSymbol(signature.parameters[pos]) : anyType; + pos < signature.parameters.length - 1 ? getTypeOfParameter(signature.parameters[pos]) : getRestTypeOfSignature(signature) : + pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : anyType; } function assignContextualParameterTypes(signature: Signature, context: Signature, mapper: TypeMapper) { @@ -20652,9 +20634,15 @@ namespace ts { function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter) { // Get type of the symbol if this is the valid symbol otherwise get type at location const symbol = getSymbolOfNode(declaration); - const type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) + let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) ? getWidenedLiteralType(getTypeOfSymbol(symbol)) : unknownType; + if (strictNullChecks && + declaration.kind === SyntaxKind.Parameter && + (declaration as ParameterDeclaration).initializer && + !(getModifierFlags(declaration) & ModifierFlags.ParameterPropertyModifier)) { + type = includeFalsyTypes(type, TypeFlags.Undefined); + } getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration, flags); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 374c7fa89d..8b49af402c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2062,13 +2062,12 @@ BranchLabel = 1 << 2, // Non-looping junction LoopLabel = 1 << 3, // Looping junction Assignment = 1 << 4, // Assignment - InitializedParameter = 1 << 5, // Parameter with initializer - TrueCondition = 1 << 6, // Condition known to be true - FalseCondition = 1 << 7, // Condition known to be false - SwitchClause = 1 << 8, // Switch statement clause - ArrayMutation = 1 << 9, // Potential array mutation - Referenced = 1 << 10, // Referenced as antecedent once - Shared = 1 << 11, // Referenced as antecedent more than once + TrueCondition = 1 << 5, // Condition known to be true + FalseCondition = 1 << 6, // Condition known to be false + SwitchClause = 1 << 7, // Switch statement clause + ArrayMutation = 1 << 8, // Potential array mutation + Referenced = 1 << 9, // Referenced as antecedent once + Shared = 1 << 10, // Referenced as antecedent more than once Label = BranchLabel | LoopLabel, Condition = TrueCondition | FalseCondition } @@ -2118,11 +2117,6 @@ antecedent: FlowNode; } - export interface FlowInitializedParameter extends FlowNode { - node: ParameterDeclaration; - antecedent: FlowNode; - } - export type FlowType = Type | IncompleteType; // Incomplete types occur during control flow analysis of loops. An IncompleteType From bb40819f7537988ac32a8b4889cd9f1c33976e22 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 27 Jan 2017 14:30:11 -0800 Subject: [PATCH 18/29] Update tests and baselines --- ...ameterAddsUndefinedWithStrictNullChecks.js | 21 +++------ ...rAddsUndefinedWithStrictNullChecks.symbols | 45 +++++++------------ ...terAddsUndefinedWithStrictNullChecks.types | 34 ++++---------- ...ameterAddsUndefinedWithStrictNullChecks.ts | 11 ++--- 4 files changed, 31 insertions(+), 80 deletions(-) diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js index 561fa65a15..d2933f1566 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js @@ -12,18 +12,13 @@ function foo1(x: string = "string", b: number) { x.length; } -function foo2(x: string | undefined = "string", b: number) { - x.length; // ok, should be narrowed to string +function foo2(x = "string", b: number) { + x.length; } -function foo3(x = "string", b: number) { - x.length; // ok, should be narrowed to string -} - -// .d.ts should have `T | undefined` for foo1, foo2, foo3 +// .d.ts should have `T | undefined` for foo1 and foo2 foo1(undefined, 1); foo2(undefined, 1); -foo3(undefined, 1); function removeUndefinedButNotFalse(x = true) { @@ -60,16 +55,11 @@ function foo1(x, b) { } function foo2(x, b) { if (x === void 0) { x = "string"; } - x.length; // ok, should be narrowed to string + x.length; } -function foo3(x, b) { - if (x === void 0) { x = "string"; } - x.length; // ok, should be narrowed to string -} -// .d.ts should have `T | undefined` for foo1, foo2, foo3 +// .d.ts should have `T | undefined` for foo1 and foo2 foo1(undefined, 1); foo2(undefined, 1); -foo3(undefined, 1); function removeUndefinedButNotFalse(x) { if (x === void 0) { x = true; } if (x === false) { @@ -93,7 +83,6 @@ declare function g(addUndefined: string | undefined, addDefined: number): number declare let total: number; declare function foo1(x: string | undefined, b: number): void; declare function foo2(x: string | undefined, b: number): void; -declare function foo3(x: string | undefined, b: number): void; declare function removeUndefinedButNotFalse(x?: boolean | undefined): false | undefined; declare const cond: boolean; declare function removeNothing(y?: boolean | undefined): boolean; diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols index f722ed3af2..6d4e02c6ab 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols @@ -46,29 +46,18 @@ function foo1(x: string = "string", b: number) { >length : Symbol(String.length, Decl(lib.d.ts, --, --)) } -function foo2(x: string | undefined = "string", b: number) { +function foo2(x = "string", b: number) { >foo2 : Symbol(foo2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 11, 1)) >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 14)) ->b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 47)) +>b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 27)) - x.length; // ok, should be narrowed to string + x.length; >x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 14)) >length : Symbol(String.length, Decl(lib.d.ts, --, --)) } -function foo3(x = "string", b: number) { ->foo3 : Symbol(foo3, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 15, 1)) ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 14)) ->b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 27)) - - x.length; // ok, should be narrowed to string ->x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 14)) ->length : Symbol(String.length, Decl(lib.d.ts, --, --)) -} - -// .d.ts should have `T | undefined` for foo1, foo2, foo3 +// .d.ts should have `T | undefined` for foo1 and foo2 foo1(undefined, 1); >foo1 : Symbol(foo1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 7, 36)) >undefined : Symbol(undefined) @@ -77,41 +66,37 @@ foo2(undefined, 1); >foo2 : Symbol(foo2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 11, 1)) >undefined : Symbol(undefined) -foo3(undefined, 1); ->foo3 : Symbol(foo3, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 15, 1)) ->undefined : Symbol(undefined) - function removeUndefinedButNotFalse(x = true) { ->removeUndefinedButNotFalse : Symbol(removeUndefinedButNotFalse, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 24, 19)) ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 27, 36)) +>removeUndefinedButNotFalse : Symbol(removeUndefinedButNotFalse, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 19, 19)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 22, 36)) if (x === false) { ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 27, 36)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 22, 36)) return x; ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 27, 36)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 22, 36)) } } declare const cond: boolean; ->cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 33, 13)) +>cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 28, 13)) function removeNothing(y = cond ? true : undefined) { ->removeNothing : Symbol(removeNothing, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 33, 28)) ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 23)) ->cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 33, 13)) +>removeNothing : Symbol(removeNothing, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 28, 28)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 29, 23)) +>cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 28, 13)) >undefined : Symbol(undefined) if (y !== undefined) { ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 23)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 29, 23)) >undefined : Symbol(undefined) if (y === false) { ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 23)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 29, 23)) return y; ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 23)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 29, 23)) } } return true; diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types index 15535eb435..1311a6e922 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types @@ -1,7 +1,7 @@ === tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts === function f(addUndefined1 = "J", addUndefined2?: number) { >f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => number ->addUndefined1 : string | undefined +>addUndefined1 : string >"J" : "J" >addUndefined2 : number | undefined @@ -17,7 +17,7 @@ function f(addUndefined1 = "J", addUndefined2?: number) { } function g(addUndefined = "J", addDefined: number) { >g : (addUndefined: string | undefined, addDefined: number) => number ->addUndefined : string | undefined +>addUndefined : string >"J" : "J" >addDefined : number @@ -62,7 +62,7 @@ total = g('c', 3) + g(undefined, 4); function foo1(x: string = "string", b: number) { >foo1 : (x: string | undefined, b: number) => void ->x : string | undefined +>x : string >"string" : "string" >b : number @@ -72,31 +72,19 @@ function foo1(x: string = "string", b: number) { >length : number } -function foo2(x: string | undefined = "string", b: number) { +function foo2(x = "string", b: number) { >foo2 : (x: string | undefined, b: number) => void ->x : string | undefined +>x : string >"string" : "string" >b : number - x.length; // ok, should be narrowed to string + x.length; >x.length : number >x : string >length : number } -function foo3(x = "string", b: number) { ->foo3 : (x: string | undefined, b: number) => void ->x : string | undefined ->"string" : "string" ->b : number - - x.length; // ok, should be narrowed to string ->x.length : number ->x : string ->length : number -} - -// .d.ts should have `T | undefined` for foo1, foo2, foo3 +// .d.ts should have `T | undefined` for foo1 and foo2 foo1(undefined, 1); >foo1(undefined, 1) : void >foo1 : (x: string | undefined, b: number) => void @@ -109,16 +97,10 @@ foo2(undefined, 1); >undefined : undefined >1 : 1 -foo3(undefined, 1); ->foo3(undefined, 1) : void ->foo3 : (x: string | undefined, b: number) => void ->undefined : undefined ->1 : 1 - function removeUndefinedButNotFalse(x = true) { >removeUndefinedButNotFalse : (x?: boolean | undefined) => false | undefined ->x : boolean | undefined +>x : boolean >true : true if (x === false) { diff --git a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts index 44f17c949e..47344e6974 100644 --- a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts +++ b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts @@ -13,18 +13,13 @@ function foo1(x: string = "string", b: number) { x.length; } -function foo2(x: string | undefined = "string", b: number) { - x.length; // ok, should be narrowed to string +function foo2(x = "string", b: number) { + x.length; } -function foo3(x = "string", b: number) { - x.length; // ok, should be narrowed to string -} - -// .d.ts should have `T | undefined` for foo1, foo2, foo3 +// .d.ts should have `T | undefined` for foo1 and foo2 foo1(undefined, 1); foo2(undefined, 1); -foo3(undefined, 1); function removeUndefinedButNotFalse(x = true) { From a235d544f3213bde39fda163a65c6147967b3a67 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 30 Jan 2017 14:59:23 -0800 Subject: [PATCH 19/29] Remove undefined from initialized+annotated parameter type --- src/compiler/checker.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a9c98d0234..199cb60c3c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3286,6 +3286,16 @@ namespace ts { return strictNullChecks && optional ? includeFalsyTypes(type, TypeFlags.Undefined) : type; } + /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ + function removeOptionalityFromAnnotation(annotatedType: Type, declaration: VariableLikeDeclaration): Type { + const annotationIncludesUndefined = strictNullChecks && + declaration.kind === SyntaxKind.Parameter && + declaration.initializer && + getFalsyFlags(annotatedType) & TypeFlags.Undefined && + !(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined); + return annotationIncludesUndefined ? getNonNullableType(annotatedType) : annotatedType; + } + // Return the inferred type for a variable, parameter, or property declaration function getTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration, includeOptionality: boolean): Type { if (declaration.flags & NodeFlags.JavaScriptFile) { @@ -3319,7 +3329,8 @@ namespace ts { // Use type from type annotation if one is present if (declaration.type) { - return addOptionality(getTypeFromTypeNode(declaration.type), /*optional*/ declaration.questionToken && includeOptionality); + const declaredType = removeOptionalityFromAnnotation(getTypeFromTypeNode(declaration.type), declaration); + return addOptionality(declaredType, /*optional*/ declaration.questionToken && includeOptionality); } if ((compilerOptions.noImplicitAny || declaration.flags & NodeFlags.JavaScriptFile) && From 7cf595a381492ad9da75ec4b4ced82c4f4b1dbdb Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 30 Jan 2017 15:00:50 -0800 Subject: [PATCH 20/29] Test removing undefined from initialized, annotated parameters --- ...ameterAddsUndefinedWithStrictNullChecks.js | 32 +++++++++-- ...rAddsUndefinedWithStrictNullChecks.symbols | 57 ++++++++++++++----- ...terAddsUndefinedWithStrictNullChecks.types | 40 ++++++++++++- ...ameterAddsUndefinedWithStrictNullChecks.ts | 16 +++++- 4 files changed, 124 insertions(+), 21 deletions(-) diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js index d2933f1566..cb556cc2ce 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js @@ -13,12 +13,24 @@ function foo1(x: string = "string", b: number) { } function foo2(x = "string", b: number) { - x.length; + x.length; // ok, should be string } -// .d.ts should have `T | undefined` for foo1 and foo2 +function foo3(x: string | undefined = "string", b: number) { + x.length; // ok, should be string +} + +function foo4(x: string | undefined = undefined, b: number) { + x; // should be string | undefined +} + + + +// .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 foo1(undefined, 1); foo2(undefined, 1); +foo3(undefined, 1); +foo4(undefined, 1); function removeUndefinedButNotFalse(x = true) { @@ -55,11 +67,21 @@ function foo1(x, b) { } function foo2(x, b) { if (x === void 0) { x = "string"; } - x.length; + x.length; // ok, should be string } -// .d.ts should have `T | undefined` for foo1 and foo2 +function foo3(x, b) { + if (x === void 0) { x = "string"; } + x.length; // ok, should be string +} +function foo4(x, b) { + if (x === void 0) { x = undefined; } + x; // should be string | undefined +} +// .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 foo1(undefined, 1); foo2(undefined, 1); +foo3(undefined, 1); +foo4(undefined, 1); function removeUndefinedButNotFalse(x) { if (x === void 0) { x = true; } if (x === false) { @@ -83,6 +105,8 @@ declare function g(addUndefined: string | undefined, addDefined: number): number declare let total: number; declare function foo1(x: string | undefined, b: number): void; declare function foo2(x: string | undefined, b: number): void; +declare function foo3(x: string | undefined, b: number): void; +declare function foo4(x: string | undefined, b: number): void; declare function removeUndefinedButNotFalse(x?: boolean | undefined): false | undefined; declare const cond: boolean; declare function removeNothing(y?: boolean | undefined): boolean; diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols index 6d4e02c6ab..fb0fce7dc7 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols @@ -51,13 +51,36 @@ function foo2(x = "string", b: number) { >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 14)) >b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 27)) - x.length; + x.length; // ok, should be string >x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) >x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 13, 14)) >length : Symbol(String.length, Decl(lib.d.ts, --, --)) } -// .d.ts should have `T | undefined` for foo1 and foo2 +function foo3(x: string | undefined = "string", b: number) { +>foo3 : Symbol(foo3, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 15, 1)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 14)) +>b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 47)) + + x.length; // ok, should be string +>x.length : Symbol(String.length, Decl(lib.d.ts, --, --)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 14)) +>length : Symbol(String.length, Decl(lib.d.ts, --, --)) +} + +function foo4(x: string | undefined = undefined, b: number) { +>foo4 : Symbol(foo4, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 19, 1)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 21, 14)) +>undefined : Symbol(undefined) +>b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 21, 48)) + + x; // should be string | undefined +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 21, 14)) +} + + + +// .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 foo1(undefined, 1); >foo1 : Symbol(foo1, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 7, 36)) >undefined : Symbol(undefined) @@ -66,37 +89,45 @@ foo2(undefined, 1); >foo2 : Symbol(foo2, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 11, 1)) >undefined : Symbol(undefined) +foo3(undefined, 1); +>foo3 : Symbol(foo3, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 15, 1)) +>undefined : Symbol(undefined) + +foo4(undefined, 1); +>foo4 : Symbol(foo4, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 19, 1)) +>undefined : Symbol(undefined) + function removeUndefinedButNotFalse(x = true) { ->removeUndefinedButNotFalse : Symbol(removeUndefinedButNotFalse, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 19, 19)) ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 22, 36)) +>removeUndefinedButNotFalse : Symbol(removeUndefinedButNotFalse, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 31, 19)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 36)) if (x === false) { ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 22, 36)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 36)) return x; ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 22, 36)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 36)) } } declare const cond: boolean; ->cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 28, 13)) +>cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 13)) function removeNothing(y = cond ? true : undefined) { ->removeNothing : Symbol(removeNothing, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 28, 28)) ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 29, 23)) ->cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 28, 13)) +>removeNothing : Symbol(removeNothing, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 28)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) +>cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 13)) >undefined : Symbol(undefined) if (y !== undefined) { ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 29, 23)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) >undefined : Symbol(undefined) if (y === false) { ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 29, 23)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) return y; ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 29, 23)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) } } return true; diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types index 1311a6e922..21ff19fc65 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types @@ -78,13 +78,37 @@ function foo2(x = "string", b: number) { >"string" : "string" >b : number - x.length; + x.length; // ok, should be string >x.length : number >x : string >length : number } -// .d.ts should have `T | undefined` for foo1 and foo2 +function foo3(x: string | undefined = "string", b: number) { +>foo3 : (x: string | undefined, b: number) => void +>x : string +>"string" : "string" +>b : number + + x.length; // ok, should be string +>x.length : number +>x : string +>length : number +} + +function foo4(x: string | undefined = undefined, b: number) { +>foo4 : (x: string | undefined, b: number) => void +>x : string | undefined +>undefined : undefined +>b : number + + x; // should be string | undefined +>x : string | undefined +} + + + +// .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 foo1(undefined, 1); >foo1(undefined, 1) : void >foo1 : (x: string | undefined, b: number) => void @@ -97,6 +121,18 @@ foo2(undefined, 1); >undefined : undefined >1 : 1 +foo3(undefined, 1); +>foo3(undefined, 1) : void +>foo3 : (x: string | undefined, b: number) => void +>undefined : undefined +>1 : 1 + +foo4(undefined, 1); +>foo4(undefined, 1) : void +>foo4 : (x: string | undefined, b: number) => void +>undefined : undefined +>1 : 1 + function removeUndefinedButNotFalse(x = true) { >removeUndefinedButNotFalse : (x?: boolean | undefined) => false | undefined diff --git a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts index 47344e6974..8abea19f6c 100644 --- a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts +++ b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts @@ -14,12 +14,24 @@ function foo1(x: string = "string", b: number) { } function foo2(x = "string", b: number) { - x.length; + x.length; // ok, should be string } -// .d.ts should have `T | undefined` for foo1 and foo2 +function foo3(x: string | undefined = "string", b: number) { + x.length; // ok, should be string +} + +function foo4(x: string | undefined = undefined, b: number) { + x; // should be string | undefined +} + + + +// .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 foo1(undefined, 1); foo2(undefined, 1); +foo3(undefined, 1); +foo4(undefined, 1); function removeUndefinedButNotFalse(x = true) { From 6f7c984c420b08d3ac633c7c2a3c010f0861580e Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 1 Feb 2017 09:41:44 -0800 Subject: [PATCH 21/29] Address PR comments 1. Add undefined only when an initialized parameter is required (not optional). 2. Create isRequiredInitializedParameter helper function 3. Call this function only once from declarationEmitter --- src/compiler/checker.ts | 22 ++++++++++------------ src/compiler/declarationEmitter.ts | 20 ++++++++++++-------- src/compiler/types.ts | 4 +++- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 199cb60c3c..ab4f961e3a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2695,7 +2695,7 @@ namespace ts { writeSpace(writer); let type = getTypeOfSymbol(p); - if (strictNullChecks && parameterNode.initializer && !(getModifierFlags(parameterNode) & ModifierFlags.ParameterPropertyModifier)) { + if (isRequiredInitializedParameter(parameterNode)) { type = includeFalsyTypes(type, TypeFlags.Undefined); } buildTypeDisplay(type, writer, enclosingDeclaration, flags, symbolStack); @@ -3182,12 +3182,6 @@ namespace ts { } return parentType; } - // In strict null checking mode, a default value of a binding pattern adds undefined, - // which should be removed to get the type of the elements - const func = getContainingFunction(declaration); - if (strictNullChecks && func && !func.body && getFalsyFlags(parentType) & TypeFlags.Undefined) { - parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined); - } let type: Type; if (pattern.kind === SyntaxKind.ObjectBindingPattern) { @@ -20554,6 +20548,13 @@ namespace ts { return false; } + function isRequiredInitializedParameter(parameter: ParameterDeclaration) { + return strictNullChecks && + !isOptionalParameter(parameter) && + parameter.initializer && + !(getModifierFlags(parameter) & ModifierFlags.ParameterPropertyModifier); + } + function getNodeCheckFlags(node: Node): NodeCheckFlags { node = getParseTreeNode(node); return node ? getNodeLinks(node).flags : undefined; @@ -20648,13 +20649,9 @@ namespace ts { let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) ? getWidenedLiteralType(getTypeOfSymbol(symbol)) : unknownType; - if (strictNullChecks && - declaration.kind === SyntaxKind.Parameter && - (declaration as ParameterDeclaration).initializer && - !(getModifierFlags(declaration) & ModifierFlags.ParameterPropertyModifier)) { + if (flags & TypeFormatFlags.AddUndefined) { type = includeFalsyTypes(type, TypeFlags.Undefined); } - getSymbolDisplayBuilder().buildTypeDisplay(type, writer, enclosingDeclaration, flags); } @@ -20753,6 +20750,7 @@ namespace ts { isTopLevelValueImportEqualsWithEntityName, isDeclarationVisible, isImplementationOfOverload, + isRequiredInitializedParameter, writeTypeOfDeclaration, writeReturnTypeOfSignatureDeclaration, writeTypeOfExpression, diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 93c4e9578f..95a56b12dd 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -322,13 +322,22 @@ namespace ts { function writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, type: TypeNode, getSymbolAccessibilityDiagnostic: GetSymbolAccessibilityDiagnostic) { writer.getSymbolAccessibilityDiagnostic = getSymbolAccessibilityDiagnostic; write(": "); - if (type) { + + // use the checker's type, not the declared type, + // for non-optional initialized parameters that aren't a parameter property + const shouldUseResolverType = declaration.kind === SyntaxKind.Parameter && + resolver.isRequiredInitializedParameter(declaration as ParameterDeclaration); + if (type && !shouldUseResolverType) { // Write the type emitType(type); } else { errorNameNode = declaration.name; - resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue, writer); + let format = TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue; + if (shouldUseResolverType) { + format |= TypeFormatFlags.AddUndefined; + } + resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, format, writer); errorNameNode = undefined; } } @@ -1594,12 +1603,7 @@ namespace ts { emitTypeOfVariableDeclarationFromTypeLiteral(node); } else if (!hasModifier(node.parent, ModifierFlags.Private)) { - // use the checker's type, not the declared type, - // for optional parameters and initialized ones that aren't a parameter property - const typeShouldAddUndefined = resolver.isOptionalParameter(node) || - node.initializer && !(getModifierFlags(node) & ModifierFlags.ParameterPropertyModifier); - const typeNode = typeShouldAddUndefined ? undefined : node.type; - writeTypeOfDeclaration(node, typeNode, getParameterDeclarationTypeVisibilityError); + writeTypeOfDeclaration(node, node.type, getParameterDeclarationTypeVisibilityError); } function getParameterDeclarationTypeVisibilityError(symbolAccessibilityResult: SymbolAccessibilityResult): SymbolAccessibilityDiagnostic { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8b49af402c..48feb6bb09 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2466,7 +2466,8 @@ InFirstTypeArgument = 0x00000100, // Writing first type argument of the instantiated type InTypeAlias = 0x00000200, // Writing type in type alias declaration UseTypeAliasValue = 0x00000400, // Serialize the type instead of using type-alias. This is needed when we emit declaration file. - SuppressAnyReturnType = 0x00000800, // If the return type is any-like, don't offer a return type. + SuppressAnyReturnType = 0x00000800, // If the return type is any-like, don't offer a return type. + AddUndefined = 0x00001000, // Add undefined to types of initialized, non-optional parameters } export const enum SymbolFormatFlags { @@ -2571,6 +2572,7 @@ isDeclarationVisible(node: Declaration): boolean; collectLinkedAliases(node: Identifier): Node[]; isImplementationOfOverload(node: FunctionLikeDeclaration): boolean; + isRequiredInitializedParameter(node: ParameterDeclaration): boolean; writeTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void; writeReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void; writeTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void; From 6328e6cfe2c7bed9d0f128b1548863668442c1f8 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 1 Feb 2017 09:43:02 -0800 Subject: [PATCH 22/29] Update baselines --- ...aultParameterAddsUndefinedWithStrictNullChecks.js | 4 ++-- ...tParameterAddsUndefinedWithStrictNullChecks.types | 12 ++++++------ .../reference/destructureOptionalParameter.js | 12 ++++++------ .../reference/destructureOptionalParameter.types | 2 +- tests/baselines/reference/optionalMethods.js | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js index cb556cc2ce..329c2fb19c 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js @@ -100,13 +100,13 @@ function removeNothing(y) { //// [defaultParameterAddsUndefinedWithStrictNullChecks.d.ts] -declare function f(addUndefined1?: string | undefined, addUndefined2?: number | undefined): number; +declare function f(addUndefined1?: string, addUndefined2?: number): number; declare function g(addUndefined: string | undefined, addDefined: number): number; declare let total: number; declare function foo1(x: string | undefined, b: number): void; declare function foo2(x: string | undefined, b: number): void; declare function foo3(x: string | undefined, b: number): void; declare function foo4(x: string | undefined, b: number): void; -declare function removeUndefinedButNotFalse(x?: boolean | undefined): false | undefined; +declare function removeUndefinedButNotFalse(x?: boolean): false | undefined; declare const cond: boolean; declare function removeNothing(y?: boolean | undefined): boolean; diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types index 21ff19fc65..d95f925989 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types @@ -1,6 +1,6 @@ === tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts === function f(addUndefined1 = "J", addUndefined2?: number) { ->f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => number +>f : (addUndefined1?: string, addUndefined2?: number | undefined) => number >addUndefined1 : string >"J" : "J" >addUndefined2 : number | undefined @@ -34,16 +34,16 @@ let total = f() + f('a', 1) + f('b') + f(undefined, 2); >f() + f('a', 1) + f('b') : number >f() + f('a', 1) : number >f() : number ->f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => number +>f : (addUndefined1?: string, addUndefined2?: number | undefined) => number >f('a', 1) : number ->f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => number +>f : (addUndefined1?: string, addUndefined2?: number | undefined) => number >'a' : "a" >1 : 1 >f('b') : number ->f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => number +>f : (addUndefined1?: string, addUndefined2?: number | undefined) => number >'b' : "b" >f(undefined, 2) : number ->f : (addUndefined1?: string | undefined, addUndefined2?: number | undefined) => number +>f : (addUndefined1?: string, addUndefined2?: number | undefined) => number >undefined : undefined >2 : 2 @@ -135,7 +135,7 @@ foo4(undefined, 1); function removeUndefinedButNotFalse(x = true) { ->removeUndefinedButNotFalse : (x?: boolean | undefined) => false | undefined +>removeUndefinedButNotFalse : (x?: boolean) => false | undefined >x : boolean >true : true diff --git a/tests/baselines/reference/destructureOptionalParameter.js b/tests/baselines/reference/destructureOptionalParameter.js index f375c41dbe..b937d58412 100644 --- a/tests/baselines/reference/destructureOptionalParameter.js +++ b/tests/baselines/reference/destructureOptionalParameter.js @@ -36,11 +36,11 @@ function f2(_a) { declare function f1({a, b}?: { a: number; b: string; -} | undefined): void; +}): void; declare function f2({a, b}?: { a: number; b: number; -} | undefined): void; +}): void; interface Type { t: void; } @@ -49,11 +49,11 @@ interface QueryMetadata { } interface QueryMetadataFactory { (selector: Type | string, {descendants, read}?: { - descendants?: boolean | undefined; + descendants?: boolean; read?: any; - } | undefined): ParameterDecorator; + }): ParameterDecorator; new (selector: Type | string, {descendants, read}?: { - descendants?: boolean | undefined; + descendants?: boolean; read?: any; - } | undefined): QueryMetadata; + }): QueryMetadata; } diff --git a/tests/baselines/reference/destructureOptionalParameter.types b/tests/baselines/reference/destructureOptionalParameter.types index c068e3b569..a72b03f593 100644 --- a/tests/baselines/reference/destructureOptionalParameter.types +++ b/tests/baselines/reference/destructureOptionalParameter.types @@ -8,7 +8,7 @@ declare function f1({ a, b }?: { a: number, b: string }): void; >b : string function f2({ a, b }: { a: number, b: number } = { a: 0, b: 0 }) { ->f2 : ({a, b}?: { a: number; b: number; } | undefined) => void +>f2 : ({a, b}?: { a: number; b: number; }) => void >a : number >b : number >a : number diff --git a/tests/baselines/reference/optionalMethods.js b/tests/baselines/reference/optionalMethods.js index 47d651d750..a3685175f6 100644 --- a/tests/baselines/reference/optionalMethods.js +++ b/tests/baselines/reference/optionalMethods.js @@ -137,7 +137,7 @@ declare class Bar { a: number; b?: number; c?: number | undefined; - constructor(d?: number | undefined, e?: number); + constructor(d?: number, e?: number); f(): number; g?(): number; h?(): number; From e03509affa543c39e24bbbffcd60b3249b18c301 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 7 Feb 2017 09:47:10 -0800 Subject: [PATCH 23/29] No subtype reduction in includeFalsyTypes It's not really needed and caused #13826. --- src/compiler/checker.ts | 2 +- .../reference/optionalParameterRetainsNull.js | 14 +++++++++ .../optionalParameterRetainsNull.symbols | 25 +++++++++++++++ .../optionalParameterRetainsNull.types | 31 +++++++++++++++++++ .../compiler/optionalParameterRetainsNull.ts | 7 +++++ 5 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/optionalParameterRetainsNull.js create mode 100644 tests/baselines/reference/optionalParameterRetainsNull.symbols create mode 100644 tests/baselines/reference/optionalParameterRetainsNull.types create mode 100644 tests/cases/compiler/optionalParameterRetainsNull.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4423b574e3..383aea994c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8540,7 +8540,7 @@ namespace ts { if (flags & TypeFlags.Void) types.push(voidType); if (flags & TypeFlags.Undefined) types.push(undefinedType); if (flags & TypeFlags.Null) types.push(nullType); - return getUnionType(types, /*subtypeReduction*/ true); + return getUnionType(types); } function removeDefinitelyFalsyTypes(type: Type): Type { diff --git a/tests/baselines/reference/optionalParameterRetainsNull.js b/tests/baselines/reference/optionalParameterRetainsNull.js new file mode 100644 index 0000000000..b8fb2c1235 --- /dev/null +++ b/tests/baselines/reference/optionalParameterRetainsNull.js @@ -0,0 +1,14 @@ +//// [optionalParameterRetainsNull.ts] +interface Bar { bar: number; foo: object | null; } + +let a = { + test (a: K, b?: Bar[K] | null) { } +}; +a.test("bar", null); // ok, null is assignable to number | null | undefined + + +//// [optionalParameterRetainsNull.js] +var a = { + test: function (a, b) { } +}; +a.test("bar", null); // ok, null is assignable to number | null | undefined diff --git a/tests/baselines/reference/optionalParameterRetainsNull.symbols b/tests/baselines/reference/optionalParameterRetainsNull.symbols new file mode 100644 index 0000000000..95786c1bdc --- /dev/null +++ b/tests/baselines/reference/optionalParameterRetainsNull.symbols @@ -0,0 +1,25 @@ +=== tests/cases/compiler/optionalParameterRetainsNull.ts === +interface Bar { bar: number; foo: object | null; } +>Bar : Symbol(Bar, Decl(optionalParameterRetainsNull.ts, 0, 0)) +>bar : Symbol(Bar.bar, Decl(optionalParameterRetainsNull.ts, 0, 15)) +>foo : Symbol(Bar.foo, Decl(optionalParameterRetainsNull.ts, 0, 29)) + +let a = { +>a : Symbol(a, Decl(optionalParameterRetainsNull.ts, 2, 3)) + + test (a: K, b?: Bar[K] | null) { } +>test : Symbol(test, Decl(optionalParameterRetainsNull.ts, 2, 9)) +>K : Symbol(K, Decl(optionalParameterRetainsNull.ts, 3, 7)) +>Bar : Symbol(Bar, Decl(optionalParameterRetainsNull.ts, 0, 0)) +>a : Symbol(a, Decl(optionalParameterRetainsNull.ts, 3, 29)) +>K : Symbol(K, Decl(optionalParameterRetainsNull.ts, 3, 7)) +>b : Symbol(b, Decl(optionalParameterRetainsNull.ts, 3, 34)) +>Bar : Symbol(Bar, Decl(optionalParameterRetainsNull.ts, 0, 0)) +>K : Symbol(K, Decl(optionalParameterRetainsNull.ts, 3, 7)) + +}; +a.test("bar", null); // ok, null is assignable to number | null | undefined +>a.test : Symbol(test, Decl(optionalParameterRetainsNull.ts, 2, 9)) +>a : Symbol(a, Decl(optionalParameterRetainsNull.ts, 2, 3)) +>test : Symbol(test, Decl(optionalParameterRetainsNull.ts, 2, 9)) + diff --git a/tests/baselines/reference/optionalParameterRetainsNull.types b/tests/baselines/reference/optionalParameterRetainsNull.types new file mode 100644 index 0000000000..30b7d61f76 --- /dev/null +++ b/tests/baselines/reference/optionalParameterRetainsNull.types @@ -0,0 +1,31 @@ +=== tests/cases/compiler/optionalParameterRetainsNull.ts === +interface Bar { bar: number; foo: object | null; } +>Bar : Bar +>bar : number +>foo : object | null +>null : null + +let a = { +>a : { test(a: K, b?: Bar[K] | null | undefined): void; } +>{ test (a: K, b?: Bar[K] | null) { }} : { test(a: K, b?: Bar[K] | null | undefined): void; } + + test (a: K, b?: Bar[K] | null) { } +>test : (a: K, b?: Bar[K] | null | undefined) => void +>K : K +>Bar : Bar +>a : K +>K : K +>b : Bar[K] | null | undefined +>Bar : Bar +>K : K +>null : null + +}; +a.test("bar", null); // ok, null is assignable to number | null | undefined +>a.test("bar", null) : void +>a.test : (a: K, b?: Bar[K] | null | undefined) => void +>a : { test(a: K, b?: Bar[K] | null | undefined): void; } +>test : (a: K, b?: Bar[K] | null | undefined) => void +>"bar" : "bar" +>null : null + diff --git a/tests/cases/compiler/optionalParameterRetainsNull.ts b/tests/cases/compiler/optionalParameterRetainsNull.ts new file mode 100644 index 0000000000..a3b50a82d2 --- /dev/null +++ b/tests/cases/compiler/optionalParameterRetainsNull.ts @@ -0,0 +1,7 @@ +// @strictNullChecks: true +interface Bar { bar: number; foo: object | null; } + +let a = { + test (a: K, b?: Bar[K] | null) { } +}; +a.test("bar", null); // ok, null is assignable to number | null | undefined From 1c7628e6534cda58de2f29eaf5a51ef9aac33ce2 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Fri, 10 Feb 2017 14:01:47 -0800 Subject: [PATCH 24/29] Improve discriminated union error messages Assignability errors for discriminated unions now check the value of the discriminant to decide which member of the union to check for assignability. Previously, assignability didn't know about discriminated unions and would check every member, issuing errors for the last member of the union if assignability failed. For example: ```ts type Square = { kind: "sq", size: number } type Rectangle = { kind: "rt", x: number, y: number } type Circle = { kind: "cr", radius: number } type Shape = | Square | Rectangle | Circle; let shape: Shape = { kind: "sq", x: 12, y: 13, } ``` `typeRelatedToSomeType` now checks whether each property in the source type is a discriminant. It finds `kind` and proceeds to look for the type in the target union that has `kind: "sq"`. If it finds it, which it does in this example (`Square`), then it checks only assignbility to `Square`. The result is that the error now says that property 'size' is missing in type `{ kind: "sq", x: number, y: number }` instead of saying that that "sq" is not assignable to type "cr" like it did before. Fixes #10867 --- src/compiler/checker.ts | 22 ++++++++++++++++++ .../discriminatedUnionErrorMessage.errors.txt | 23 +++++++++++++++++++ .../discriminatedUnionErrorMessage.js | 21 +++++++++++++++++ .../discriminatedUnionErrorMessage.ts | 12 ++++++++++ 4 files changed, 78 insertions(+) create mode 100644 tests/baselines/reference/discriminatedUnionErrorMessage.errors.txt create mode 100644 tests/baselines/reference/discriminatedUnionErrorMessage.js create mode 100644 tests/cases/compiler/discriminatedUnionErrorMessage.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ac1135788c..3735a01849 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7771,6 +7771,11 @@ namespace ts { if (target.flags & TypeFlags.Union && containsType(targetTypes, source)) { return Ternary.True; } + const discriminantType = findMatchingDiscriminantType(source, target); + if (discriminantType) { + return isRelatedTo(source, discriminantType, reportErrors); + } + const len = targetTypes.length; for (let i = 0; i < len; i++) { const related = isRelatedTo(source, targetTypes[i], reportErrors && i === len - 1); @@ -7781,6 +7786,23 @@ namespace ts { return Ternary.False; } + function findMatchingDiscriminantType(source: Type, target: UnionOrIntersectionType) { + const sourceProperties = getPropertiesOfObjectType(source); + if (sourceProperties) { + for (const sourceProperty of sourceProperties) { + if (isDiscriminantProperty(target, sourceProperty.name)) { + const sourceType = getTypeOfSymbol(sourceProperty); + for (const type of target.types) { + const targetType = getTypeOfPropertyOfType(type, sourceProperty.name); + if (targetType && isRelatedTo(sourceType, targetType)) { + return type; + } + } + } + } + } + } + function typeRelatedToEachType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { let result = Ternary.True; const targetTypes = target.types; diff --git a/tests/baselines/reference/discriminatedUnionErrorMessage.errors.txt b/tests/baselines/reference/discriminatedUnionErrorMessage.errors.txt new file mode 100644 index 0000000000..6f1eb511d3 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionErrorMessage.errors.txt @@ -0,0 +1,23 @@ +tests/cases/compiler/discriminatedUnionErrorMessage.ts(8,5): error TS2322: Type '{ kind: "sq"; x: number; y: number; }' is not assignable to type 'Shape'. + Type '{ kind: "sq"; x: number; y: number; }' is not assignable to type 'Square'. + Property 'size' is missing in type '{ kind: "sq"; x: number; y: number; }'. + + +==== tests/cases/compiler/discriminatedUnionErrorMessage.ts (1 errors) ==== + type Square = { kind: "sq", size: number } + type Rectangle = { kind: "rt", x: number, y: number } + type Circle = { kind: "cr", radius: number } + type Shape = + | Square + | Rectangle + | Circle; + let shape: Shape = { + ~~~~~ +!!! error TS2322: Type '{ kind: "sq"; x: number; y: number; }' is not assignable to type 'Shape'. +!!! error TS2322: Type '{ kind: "sq"; x: number; y: number; }' is not assignable to type 'Square'. +!!! error TS2322: Property 'size' is missing in type '{ kind: "sq"; x: number; y: number; }'. + kind: "sq", + x: 12, + y: 13, + } + \ No newline at end of file diff --git a/tests/baselines/reference/discriminatedUnionErrorMessage.js b/tests/baselines/reference/discriminatedUnionErrorMessage.js new file mode 100644 index 0000000000..10c94b19a4 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionErrorMessage.js @@ -0,0 +1,21 @@ +//// [discriminatedUnionErrorMessage.ts] +type Square = { kind: "sq", size: number } +type Rectangle = { kind: "rt", x: number, y: number } +type Circle = { kind: "cr", radius: number } +type Shape = + | Square + | Rectangle + | Circle; +let shape: Shape = { + kind: "sq", + x: 12, + y: 13, +} + + +//// [discriminatedUnionErrorMessage.js] +var shape = { + kind: "sq", + x: 12, + y: 13 +}; diff --git a/tests/cases/compiler/discriminatedUnionErrorMessage.ts b/tests/cases/compiler/discriminatedUnionErrorMessage.ts new file mode 100644 index 0000000000..eb2b08e943 --- /dev/null +++ b/tests/cases/compiler/discriminatedUnionErrorMessage.ts @@ -0,0 +1,12 @@ +type Square = { kind: "sq", size: number } +type Rectangle = { kind: "rt", x: number, y: number } +type Circle = { kind: "cr", radius: number } +type Shape = + | Square + | Rectangle + | Circle; +let shape: Shape = { + kind: "sq", + x: 12, + y: 13, +} From 11929e33ed3514e2dc09a29ff065e4525d331587 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 13 Feb 2017 12:54:58 -0800 Subject: [PATCH 25/29] Address PR comments --- src/compiler/checker.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3735a01849..6f21335567 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7771,18 +7771,14 @@ namespace ts { if (target.flags & TypeFlags.Union && containsType(targetTypes, source)) { return Ternary.True; } - const discriminantType = findMatchingDiscriminantType(source, target); - if (discriminantType) { - return isRelatedTo(source, discriminantType, reportErrors); - } - - const len = targetTypes.length; - for (let i = 0; i < len; i++) { - const related = isRelatedTo(source, targetTypes[i], reportErrors && i === len - 1); + for (const type of targetTypes) { + const related = isRelatedTo(source, type, /*reportErrors*/ false); if (related) { return related; } } + const discriminantType = findMatchingDiscriminantType(source, target); + isRelatedTo(source, discriminantType || targetTypes[targetTypes.length - 1], reportErrors); return Ternary.False; } From c2cd4f66e715e89e9c7959064f0a4465d59b0443 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 13 Feb 2017 13:21:12 -0800 Subject: [PATCH 26/29] Address PR comments and fix lint --- src/compiler/checker.ts | 3 +-- src/compiler/declarationEmitter.ts | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ab4f961e3a..3517f407f8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3167,8 +3167,7 @@ namespace ts { /** Return the inferred type for a binding element */ function getTypeForBindingElement(declaration: BindingElement): Type { const pattern = declaration.parent; - let parentType = getTypeForBindingElementParent(pattern.parent); - + const parentType = getTypeForBindingElementParent(pattern.parent); // If parent has the unknown (error) type, then so does this binding element if (parentType === unknownType) { return unknownType; diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 95a56b12dd..de8be5a27e 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -333,10 +333,8 @@ namespace ts { } else { errorNameNode = declaration.name; - let format = TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue; - if (shouldUseResolverType) { - format |= TypeFormatFlags.AddUndefined; - } + const format = TypeFormatFlags.UseTypeOfFunction | TypeFormatFlags.UseTypeAliasValue | + (shouldUseResolverType ? TypeFormatFlags.AddUndefined : 0); resolver.writeTypeOfDeclaration(declaration, enclosingDeclaration, format, writer); errorNameNode = undefined; } From 271ca80c75e30132cc2549a79f562a2abbb432a3 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 13 Feb 2017 13:35:07 -0800 Subject: [PATCH 27/29] Address PR comments --- src/compiler/checker.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6f21335567..6f352c95ae 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7777,8 +7777,10 @@ namespace ts { return related; } } - const discriminantType = findMatchingDiscriminantType(source, target); - isRelatedTo(source, discriminantType || targetTypes[targetTypes.length - 1], reportErrors); + if (reportErrors) { + const discriminantType = findMatchingDiscriminantType(source, target); + isRelatedTo(source, discriminantType || targetTypes[targetTypes.length - 1], /*reportErrors*/ true); + } return Ternary.False; } From 58b8a54e5fab608ce76d9d738334d80ea7ad38ac Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 13 Feb 2017 13:38:04 -0800 Subject: [PATCH 28/29] fix build break (#14049) --- tests/baselines/reference/packageJsonMain.js | 1 + tests/baselines/reference/packageJsonMain_isNonRecursive.js | 1 + tests/baselines/reference/typingsLookup4.js | 1 + 3 files changed, 3 insertions(+) diff --git a/tests/baselines/reference/packageJsonMain.js b/tests/baselines/reference/packageJsonMain.js index c7f63af27f..96509ab639 100644 --- a/tests/baselines/reference/packageJsonMain.js +++ b/tests/baselines/reference/packageJsonMain.js @@ -28,6 +28,7 @@ foo + bar + baz; //// [a.js] "use strict"; +exports.__esModule = true; var foo = require("foo"); var bar = require("bar"); var baz = require("baz"); diff --git a/tests/baselines/reference/packageJsonMain_isNonRecursive.js b/tests/baselines/reference/packageJsonMain_isNonRecursive.js index edd56b359e..97d7bbdf05 100644 --- a/tests/baselines/reference/packageJsonMain_isNonRecursive.js +++ b/tests/baselines/reference/packageJsonMain_isNonRecursive.js @@ -16,3 +16,4 @@ import foo = require("foo"); //// [a.js] "use strict"; +exports.__esModule = true; diff --git a/tests/baselines/reference/typingsLookup4.js b/tests/baselines/reference/typingsLookup4.js index b3bb2ed5de..d2424644d1 100644 --- a/tests/baselines/reference/typingsLookup4.js +++ b/tests/baselines/reference/typingsLookup4.js @@ -38,6 +38,7 @@ exports.__esModule = true; exports.l = 2; //// [index.js] "use strict"; +exports.__esModule = true; exports.m = 3; //// [a.js] "use strict"; From f673f48fad7eaff4dadb09e90d42a321c072a3b0 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 13 Feb 2017 14:36:12 -0800 Subject: [PATCH 29/29] inject pre-finally and after-finally edges into flow graph to possible ignore pre-finally during flow walk (#13845) --- src/compiler/binder.ts | 35 ++++++++++++++++++- src/compiler/checker.ts | 20 ++++++++++- src/compiler/types.ts | 15 ++++++++ .../baselines/reference/flowAfterFinally1.js | 25 +++++++++++++ .../reference/flowAfterFinally1.symbols | 29 +++++++++++++++ .../reference/flowAfterFinally1.types | 33 +++++++++++++++++ tests/cases/compiler/flowAfterFinally1.ts | 14 ++++++++ 7 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/flowAfterFinally1.js create mode 100644 tests/baselines/reference/flowAfterFinally1.symbols create mode 100644 tests/baselines/reference/flowAfterFinally1.types create mode 100644 tests/cases/compiler/flowAfterFinally1.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 04dc4011a1..41bf7b58c1 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1045,7 +1045,35 @@ namespace ts { if (node.finallyBlock) { // in finally flow is combined from pre-try/flow from try/flow from catch // pre-flow is necessary to make sure that finally is reachable even if finally flows in both try and finally blocks are unreachable - addAntecedent(preFinallyLabel, preTryFlow); + + // also for finally blocks we inject two extra edges into the flow graph. + // first -> edge that connects pre-try flow with the label at the beginning of the finally block, it has lock associated with it + // second -> edge that represents post-finally flow. + // these edges are used in following scenario: + // let a; (1) + // try { a = someOperation(); (2)} + // finally { (3) console.log(a) } (4) + // (5) a + + // flow graph for this case looks roughly like this (arrows show ): + // (1-pre-try-flow) <--.. <-- (2-post-try-flow) + // ^ ^ + // |*****(3-pre-finally-label) -----| + // ^ + // |-- ... <-- (4-post-finally-label) <--- (5) + // In case when we walk the flow starting from inside the finally block we want to take edge '*****' into account + // since it ensures that finally is always reachable. However when we start outside the finally block and go through label (5) + // then edge '*****' should be discarded because label 4 is only reachable if post-finally label-4 is reachable + // Simply speaking code inside finally block is treated as reachable as pre-try-flow + // since we conservatively assume that any line in try block can throw or return in which case we'll enter finally. + // However code after finally is reachable only if control flow was not abrupted in try/catch or finally blocks - it should be composed from + // final flows of these blocks without taking pre-try flow into account. + // + // 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); + currentFlow = finishFlowLabel(preFinallyLabel); bind(node.finallyBlock); // if flow after finally is unreachable - keep it @@ -1061,6 +1089,11 @@ namespace ts { : unreachableFlow; } } + if (!(currentFlow.flags & FlowFlags.Unreachable)) { + const afterFinallyFlow: AfterFinallyFlow = { flags: FlowFlags.AfterFinally, antecedent: currentFlow }; + preFinallyFlow.lock = afterFinallyFlow; + currentFlow = afterFinallyFlow; + } } else { currentFlow = finishFlowLabel(preFinallyLabel); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fadf49aa16..2b059e29d0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -9916,7 +9916,19 @@ namespace ts { } } let type: FlowType; - if (flow.flags & FlowFlags.Assignment) { + if (flow.flags & FlowFlags.AfterFinally) { + // block flow edge: finally -> pre-try (for larger explanation check comment in binder.ts - bindTryStatement + (flow).locked = true; + type = getTypeAtFlowNode((flow).antecedent); + (flow).locked = false; + } + else if (flow.flags & FlowFlags.PreFinally) { + // locked pre-finally flows are filtered out in getTypeAtFlowBranchLabel + // so here just redirect to antecedent + flow = (flow).antecedent; + continue; + } + else if (flow.flags & FlowFlags.Assignment) { type = getTypeAtFlowAssignment(flow); if (!type) { flow = (flow).antecedent; @@ -10072,6 +10084,12 @@ namespace ts { let subtypeReduction = false; let seenIncomplete = false; for (const antecedent of flow.antecedents) { + if (antecedent.flags & FlowFlags.PreFinally && (antecedent).lock.locked) { + // if flow correspond to branch from pre-try to finally and this branch is locked - this means that + // we initially have started following the flow outside the finally block. + // in this case we should ignore this branch. + continue; + } const flowType = getTypeAtFlowNode(antecedent); const type = getTypeFromFlowType(flowType); // If the type at a particular antecedent path is the declared type and the diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7fb71d12b9..4d8dcaa8ad 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2071,10 +2071,25 @@ ArrayMutation = 1 << 8, // Potential array mutation Referenced = 1 << 9, // Referenced as antecedent once 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 Label = BranchLabel | LoopLabel, Condition = TrueCondition | FalseCondition } + export interface FlowLock { + locked?: boolean; + } + + export interface AfterFinallyFlow extends FlowNode, FlowLock { + antecedent: FlowNode; + } + + export interface PreFinallyFlow extends FlowNode { + antecedent: FlowNode; + lock: FlowLock; + } + export interface FlowNode { flags: FlowFlags; id?: number; // Node id used by flow type cache in checker diff --git a/tests/baselines/reference/flowAfterFinally1.js b/tests/baselines/reference/flowAfterFinally1.js new file mode 100644 index 0000000000..93f3425d50 --- /dev/null +++ b/tests/baselines/reference/flowAfterFinally1.js @@ -0,0 +1,25 @@ +//// [flowAfterFinally1.ts] +declare function openFile(): void +declare function closeFile(): void +declare function someOperation(): {} + +var result: {} +openFile() +try { + result = someOperation() +} finally { + closeFile() +} + +result // should not error here + +//// [flowAfterFinally1.js] +var result; +openFile(); +try { + result = someOperation(); +} +finally { + closeFile(); +} +result; // should not error here diff --git a/tests/baselines/reference/flowAfterFinally1.symbols b/tests/baselines/reference/flowAfterFinally1.symbols new file mode 100644 index 0000000000..ad0ffbeb5c --- /dev/null +++ b/tests/baselines/reference/flowAfterFinally1.symbols @@ -0,0 +1,29 @@ +=== tests/cases/compiler/flowAfterFinally1.ts === +declare function openFile(): void +>openFile : Symbol(openFile, Decl(flowAfterFinally1.ts, 0, 0)) + +declare function closeFile(): void +>closeFile : Symbol(closeFile, Decl(flowAfterFinally1.ts, 0, 33)) + +declare function someOperation(): {} +>someOperation : Symbol(someOperation, Decl(flowAfterFinally1.ts, 1, 34)) + +var result: {} +>result : Symbol(result, Decl(flowAfterFinally1.ts, 4, 3)) + +openFile() +>openFile : Symbol(openFile, Decl(flowAfterFinally1.ts, 0, 0)) + +try { + result = someOperation() +>result : Symbol(result, Decl(flowAfterFinally1.ts, 4, 3)) +>someOperation : Symbol(someOperation, Decl(flowAfterFinally1.ts, 1, 34)) + +} finally { + closeFile() +>closeFile : Symbol(closeFile, Decl(flowAfterFinally1.ts, 0, 33)) +} + +result // should not error here +>result : Symbol(result, Decl(flowAfterFinally1.ts, 4, 3)) + diff --git a/tests/baselines/reference/flowAfterFinally1.types b/tests/baselines/reference/flowAfterFinally1.types new file mode 100644 index 0000000000..93aae4764b --- /dev/null +++ b/tests/baselines/reference/flowAfterFinally1.types @@ -0,0 +1,33 @@ +=== tests/cases/compiler/flowAfterFinally1.ts === +declare function openFile(): void +>openFile : () => void + +declare function closeFile(): void +>closeFile : () => void + +declare function someOperation(): {} +>someOperation : () => {} + +var result: {} +>result : {} + +openFile() +>openFile() : void +>openFile : () => void + +try { + result = someOperation() +>result = someOperation() : {} +>result : {} +>someOperation() : {} +>someOperation : () => {} + +} finally { + closeFile() +>closeFile() : void +>closeFile : () => void +} + +result // should not error here +>result : {} + diff --git a/tests/cases/compiler/flowAfterFinally1.ts b/tests/cases/compiler/flowAfterFinally1.ts new file mode 100644 index 0000000000..74df6a1be2 --- /dev/null +++ b/tests/cases/compiler/flowAfterFinally1.ts @@ -0,0 +1,14 @@ +// @strictNullChecks: true +declare function openFile(): void +declare function closeFile(): void +declare function someOperation(): {} + +var result: {} +openFile() +try { + result = someOperation() +} finally { + closeFile() +} + +result // should not error here \ No newline at end of file