From 6543048097a69a589dee0a3aab9503e5b58c46f0 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Wed, 21 Dec 2016 14:57:39 -0800 Subject: [PATCH] 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 4aa55d3dd70..60af7a1267f 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 3b0329881fd..550fab02679 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 1ee59149b2c..2363b76cb45 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