From eaca169b114047cb6c663fbbee4ade4b493a031f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 6 Mar 2017 16:21:54 -0800 Subject: [PATCH 1/6] Remove optionality from the initial type of default-valued parameters When narrowing, remove optionality from the initial type of parameters with initialisers. Note that the type of the initialiser is not used; its presence just means that the initial type of the parameter can't contain undefined. It could contain any other member of a declared union type. --- src/compiler/checker.ts | 43 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5d45af57343..f17f079008b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3368,16 +3368,6 @@ 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) { @@ -3412,7 +3402,7 @@ namespace ts { // Use type from type annotation if one is present if (declaration.type) { - const declaredType = removeOptionalityFromAnnotation(getTypeFromTypeNode(declaration.type), declaration); + const declaredType = getTypeFromTypeNode(declaration.type); return addOptionality(declaredType, /*optional*/ declaration.questionToken && includeOptionality); } @@ -10248,14 +10238,12 @@ namespace ts { return false; } - function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, flowContainer: Node) { + function isFlowNarrowable(reference: Node, type: Type, couldBeUninitialized?: boolean) { + return reference.flowNode && (type.flags & TypeFlags.Narrowable || couldBeUninitialized); + } + + function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node) { let key: string; - if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) { - return declaredType; - } - const initialType = assumeInitialized ? declaredType : - declaredType === autoType || declaredType === autoArrayType ? undefinedType : - includeFalsyTypes(declaredType, TypeFlags.Undefined); const visitedFlowStart = visitedFlowCount; const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); visitedFlowCount = visitedFlowStart; @@ -10934,6 +10922,16 @@ namespace ts { return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0 && getTypeOfSymbol(symbol) !== autoArrayType; } + /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ + function removeOptionalityFromDeclaredType(declaredType: Type, declaration: VariableLikeDeclaration): Type { + const annotationIncludesUndefined = strictNullChecks && + declaration.kind === SyntaxKind.Parameter && + declaration.initializer && + getFalsyFlags(declaredType) & TypeFlags.Undefined && + !(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined); + return annotationIncludesUndefined ? getNonNullableType(declaredType) : declaredType; + } + function checkIdentifier(node: Identifier): Type { const symbol = getResolvedSymbol(node); if (symbol === unknownSymbol) { @@ -11052,7 +11050,10 @@ namespace ts { const assumeInitialized = isParameter || isOuterVariable || type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & TypeFlags.Any) !== 0 || isInTypeQuery(node)) || isInAmbientContext(declaration); - const flowType = getFlowTypeOfReference(node, type, assumeInitialized, flowContainer); + const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, getRootDeclaration(declaration) as VariableLikeDeclaration) : type) : + type === autoType || type === autoArrayType ? undefinedType : + includeFalsyTypes(type, TypeFlags.Undefined); + const flowType = isFlowNarrowable(node, type, !assumeInitialized) ? getFlowTypeOfReference(node, type, initialType, flowContainer) : type; // A variable is considered uninitialized when it is possible to analyze the entire control flow graph // from declaration to use, and when the variable's declared type doesn't include undefined but the // control flow based type does include undefined. @@ -11318,7 +11319,7 @@ namespace ts { if (isClassLike(container.parent)) { const symbol = getSymbolOfNode(container.parent); const type = hasModifier(container, ModifierFlags.Static) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; - return getFlowTypeOfReference(node, type, /*assumeInitialized*/ true, /*flowContainer*/ undefined); + return isFlowNarrowable(node, type) ? getFlowTypeOfReference(node, type) : type; } if (isInJavaScriptFile(node)) { @@ -13309,7 +13310,7 @@ namespace ts { !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { return propType; } - const flowType = getFlowTypeOfReference(node, propType, /*assumeInitialized*/ true, /*flowContainer*/ undefined); + const flowType = isFlowNarrowable(node, propType) ? getFlowTypeOfReference(node, propType) : propType; return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; } From 533ce824e8235e7545d9d107fe3112c77b6e3cc3 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 6 Mar 2017 16:24:44 -0800 Subject: [PATCH 2/6] Add assignability tests for initialised parameters --- ...ameterAddsUndefinedWithStrictNullChecks.js | 4 ++ ...rAddsUndefinedWithStrictNullChecks.symbols | 40 +++++++++++-------- ...terAddsUndefinedWithStrictNullChecks.types | 12 +++++- ...ameterAddsUndefinedWithStrictNullChecks.ts | 2 + 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js index 329c2fb19ce..7f9cf553fe4 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js @@ -18,10 +18,12 @@ function foo2(x = "string", b: number) { function foo3(x: string | undefined = "string", b: number) { x.length; // ok, should be string + x = undefined; } function foo4(x: string | undefined = undefined, b: number) { x; // should be string | undefined + x = undefined; } @@ -72,10 +74,12 @@ function foo2(x, b) { function foo3(x, b) { if (x === void 0) { x = "string"; } x.length; // ok, should be string + x = undefined; } function foo4(x, b) { if (x === void 0) { x = undefined; } x; // should be string | undefined + x = undefined; } // .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 foo1(undefined, 1); diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols index fb0fce7dc7f..a484377f3f4 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols @@ -66,16 +66,24 @@ function foo3(x: string | undefined = "string", b: number) { >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, --, --)) + + x = undefined; +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 17, 14)) +>undefined : Symbol(undefined) } function foo4(x: string | undefined = undefined, b: number) { ->foo4 : Symbol(foo4, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 19, 1)) ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 21, 14)) +>foo4 : Symbol(foo4, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 20, 1)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 22, 14)) >undefined : Symbol(undefined) ->b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 21, 48)) +>b : Symbol(b, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 22, 48)) x; // should be string | undefined ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 21, 14)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 22, 14)) + + x = undefined; +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 22, 14)) +>undefined : Symbol(undefined) } @@ -94,40 +102,40 @@ foo3(undefined, 1); >undefined : Symbol(undefined) foo4(undefined, 1); ->foo4 : Symbol(foo4, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 19, 1)) +>foo4 : Symbol(foo4, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 20, 1)) >undefined : Symbol(undefined) function removeUndefinedButNotFalse(x = true) { ->removeUndefinedButNotFalse : Symbol(removeUndefinedButNotFalse, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 31, 19)) ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 36)) +>removeUndefinedButNotFalse : Symbol(removeUndefinedButNotFalse, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 33, 19)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 36, 36)) if (x === false) { ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 36)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 36, 36)) return x; ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 34, 36)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 36, 36)) } } declare const cond: boolean; ->cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 13)) +>cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 42, 13)) function removeNothing(y = cond ? true : undefined) { ->removeNothing : Symbol(removeNothing, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 28)) ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) ->cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 13)) +>removeNothing : Symbol(removeNothing, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 42, 28)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 43, 23)) +>cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 42, 13)) >undefined : Symbol(undefined) if (y !== undefined) { ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 43, 23)) >undefined : Symbol(undefined) if (y === false) { ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 43, 23)) return y; ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 41, 23)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 43, 23)) } } return true; diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types index d95f9259891..54b9cdf2a9b 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types @@ -86,7 +86,7 @@ function foo2(x = "string", b: number) { function foo3(x: string | undefined = "string", b: number) { >foo3 : (x: string | undefined, b: number) => void ->x : string +>x : string | undefined >"string" : "string" >b : number @@ -94,6 +94,11 @@ function foo3(x: string | undefined = "string", b: number) { >x.length : number >x : string >length : number + + x = undefined; +>x = undefined : undefined +>x : string | undefined +>undefined : undefined } function foo4(x: string | undefined = undefined, b: number) { @@ -104,6 +109,11 @@ function foo4(x: string | undefined = undefined, b: number) { x; // should be string | undefined >x : string | undefined + + x = undefined; +>x = undefined : undefined +>x : string | undefined +>undefined : undefined } diff --git a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts index 8abea19f6cd..04a9e668b32 100644 --- a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts +++ b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts @@ -19,10 +19,12 @@ function foo2(x = "string", b: number) { function foo3(x: string | undefined = "string", b: number) { x.length; // ok, should be string + x = undefined; } function foo4(x: string | undefined = undefined, b: number) { x; // should be string | undefined + x = undefined; } From 36513f21aba6b77ab1147320c82ac218c3c3df97 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 7 Mar 2017 09:14:51 -0800 Subject: [PATCH 3/6] Remove only undefined, not null | undefined, from declared type --- src/compiler/checker.ts | 2 +- .../defaultParameterAddsUndefinedWithStrictNullChecks.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f17f079008b..8ba023ce05a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10929,7 +10929,7 @@ namespace ts { declaration.initializer && getFalsyFlags(declaredType) & TypeFlags.Undefined && !(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined); - return annotationIncludesUndefined ? getNonNullableType(declaredType) : declaredType; + return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType; } function checkIdentifier(node: Identifier): Type { diff --git a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts index 04a9e668b32..d2e167c83a3 100644 --- a/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts +++ b/tests/cases/compiler/defaultParameterAddsUndefinedWithStrictNullChecks.ts @@ -27,6 +27,13 @@ function foo4(x: string | undefined = undefined, b: number) { x = undefined; } +type OptionalNullableString = string | null | undefined; +function allowsNull(val: OptionalNullableString = "") { + val = null; + val = 'string and null are both ok'; +} +allowsNull(null); // still allows passing null + // .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 From 2325fda8a65b140e7b9b3c4b4e1e2f7dd7872629 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 7 Mar 2017 09:28:51 -0800 Subject: [PATCH 4/6] Move isFlowNarrowable call inside getFlowTypeOfReference --- src/compiler/checker.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8ba023ce05a..fbd3ac458f0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10242,8 +10242,11 @@ namespace ts { return reference.flowNode && (type.flags & TypeFlags.Narrowable || couldBeUninitialized); } - function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node) { + function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) { let key: string; + if (!isFlowNarrowable(reference, declaredType, couldBeUninitialized)) { + return declaredType; + } const visitedFlowStart = visitedFlowCount; const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); visitedFlowCount = visitedFlowStart; @@ -11053,7 +11056,7 @@ namespace ts { const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, getRootDeclaration(declaration) as VariableLikeDeclaration) : type) : type === autoType || type === autoArrayType ? undefinedType : includeFalsyTypes(type, TypeFlags.Undefined); - const flowType = isFlowNarrowable(node, type, !assumeInitialized) ? getFlowTypeOfReference(node, type, initialType, flowContainer) : type; + const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer, !assumeInitialized); // A variable is considered uninitialized when it is possible to analyze the entire control flow graph // from declaration to use, and when the variable's declared type doesn't include undefined but the // control flow based type does include undefined. @@ -11319,7 +11322,7 @@ namespace ts { if (isClassLike(container.parent)) { const symbol = getSymbolOfNode(container.parent); const type = hasModifier(container, ModifierFlags.Static) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; - return isFlowNarrowable(node, type) ? getFlowTypeOfReference(node, type) : type; + return getFlowTypeOfReference(node, type); } if (isInJavaScriptFile(node)) { @@ -13310,7 +13313,7 @@ namespace ts { !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { return propType; } - const flowType = isFlowNarrowable(node, propType) ? getFlowTypeOfReference(node, propType) : propType; + const flowType = getFlowTypeOfReference(node, propType); return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; } From c1f4c9c54360cad8ecffa8ab5524168ec7a55ab7 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 7 Mar 2017 09:29:29 -0800 Subject: [PATCH 5/6] Update baselines --- ...ameterAddsUndefinedWithStrictNullChecks.js | 15 +++++++ ...rAddsUndefinedWithStrictNullChecks.symbols | 39 +++++++++++++------ ...terAddsUndefinedWithStrictNullChecks.types | 25 ++++++++++++ 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js index 7f9cf553fe4..0477481c95c 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.js @@ -26,6 +26,13 @@ function foo4(x: string | undefined = undefined, b: number) { x = undefined; } +type OptionalNullableString = string | null | undefined; +function allowsNull(val: OptionalNullableString = "") { + val = null; + val = 'string and null are both ok'; +} +allowsNull(null); // still allows passing null + // .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 @@ -81,6 +88,12 @@ function foo4(x, b) { x; // should be string | undefined x = undefined; } +function allowsNull(val) { + if (val === void 0) { val = ""; } + val = null; + val = 'string and null are both ok'; +} +allowsNull(null); // still allows passing null // .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 foo1(undefined, 1); foo2(undefined, 1); @@ -111,6 +124,8 @@ 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 type OptionalNullableString = string | null | undefined; +declare function allowsNull(val?: OptionalNullableString): void; 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.symbols b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols index a484377f3f4..80abc32ee73 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.symbols @@ -86,6 +86,23 @@ function foo4(x: string | undefined = undefined, b: number) { >undefined : Symbol(undefined) } +type OptionalNullableString = string | null | undefined; +>OptionalNullableString : Symbol(OptionalNullableString, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 25, 1)) + +function allowsNull(val: OptionalNullableString = "") { +>allowsNull : Symbol(allowsNull, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 27, 56)) +>val : Symbol(val, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 28, 20)) +>OptionalNullableString : Symbol(OptionalNullableString, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 25, 1)) + + val = null; +>val : Symbol(val, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 28, 20)) + + val = 'string and null are both ok'; +>val : Symbol(val, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 28, 20)) +} +allowsNull(null); // still allows passing null +>allowsNull : Symbol(allowsNull, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 27, 56)) + // .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 @@ -107,35 +124,35 @@ foo4(undefined, 1); function removeUndefinedButNotFalse(x = true) { ->removeUndefinedButNotFalse : Symbol(removeUndefinedButNotFalse, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 33, 19)) ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 36, 36)) +>removeUndefinedButNotFalse : Symbol(removeUndefinedButNotFalse, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 40, 19)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 43, 36)) if (x === false) { ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 36, 36)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 43, 36)) return x; ->x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 36, 36)) +>x : Symbol(x, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 43, 36)) } } declare const cond: boolean; ->cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 42, 13)) +>cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 49, 13)) function removeNothing(y = cond ? true : undefined) { ->removeNothing : Symbol(removeNothing, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 42, 28)) ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 43, 23)) ->cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 42, 13)) +>removeNothing : Symbol(removeNothing, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 49, 28)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 50, 23)) +>cond : Symbol(cond, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 49, 13)) >undefined : Symbol(undefined) if (y !== undefined) { ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 43, 23)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 50, 23)) >undefined : Symbol(undefined) if (y === false) { ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 43, 23)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 50, 23)) return y; ->y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 43, 23)) +>y : Symbol(y, Decl(defaultParameterAddsUndefinedWithStrictNullChecks.ts, 50, 23)) } } return true; diff --git a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types index 54b9cdf2a9b..8b8aea6370f 100644 --- a/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types +++ b/tests/baselines/reference/defaultParameterAddsUndefinedWithStrictNullChecks.types @@ -116,6 +116,31 @@ function foo4(x: string | undefined = undefined, b: number) { >undefined : undefined } +type OptionalNullableString = string | null | undefined; +>OptionalNullableString : OptionalNullableString +>null : null + +function allowsNull(val: OptionalNullableString = "") { +>allowsNull : (val?: OptionalNullableString) => void +>val : OptionalNullableString +>OptionalNullableString : OptionalNullableString +>"" : "" + + val = null; +>val = null : null +>val : OptionalNullableString +>null : null + + val = 'string and null are both ok'; +>val = 'string and null are both ok' : "string and null are both ok" +>val : OptionalNullableString +>'string and null are both ok' : "string and null are both ok" +} +allowsNull(null); // still allows passing null +>allowsNull(null) : void +>allowsNull : (val?: OptionalNullableString) => void +>null : null + // .d.ts should have `string | undefined` for foo1, foo2, foo3 and foo4 From 24c8de21c471c2a866a5dff7ce006343bebae52e Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 7 Mar 2017 10:20:02 -0800 Subject: [PATCH 6/6] Inline isFlowNarrowable --- src/compiler/checker.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fbd3ac458f0..932b2e9c331 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10238,13 +10238,9 @@ namespace ts { return false; } - function isFlowNarrowable(reference: Node, type: Type, couldBeUninitialized?: boolean) { - return reference.flowNode && (type.flags & TypeFlags.Narrowable || couldBeUninitialized); - } - function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) { let key: string; - if (!isFlowNarrowable(reference, declaredType, couldBeUninitialized)) { + if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) { return declaredType; } const visitedFlowStart = visitedFlowCount;