From eaca169b114047cb6c663fbbee4ade4b493a031f Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 6 Mar 2017 16:21:54 -0800 Subject: [PATCH] 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 5d45af5734..f17f079008 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; }