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.
This commit is contained in:
Nathan Shively-Sanders
2016-12-21 14:57:39 -08:00
parent 61c742ad6a
commit 6543048097
3 changed files with 54 additions and 11 deletions

View File

@@ -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 <FlowAssignment>{
flags: FlowFlags.Assignment,
@@ -819,6 +819,15 @@ namespace ts {
};
}
function createFlowInitializedParameter(antecedent: FlowNode, node: ParameterDeclaration): FlowNode {
setFlowNodeReferenced(antecedent);
return <FlowInitializedParameter>{
flags: FlowFlags.InitializedParameter,
antecedent,
node
};
}
function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode {
setFlowNodeReferenced(antecedent);
return <FlowArrayMutation>{
@@ -2312,7 +2321,7 @@ namespace ts {
else {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes);
if (node.initializer) {
currentFlow = createFlowAssignment(currentFlow, node);
currentFlow = createFlowInitializedParameter(currentFlow, node);
}
}

View File

@@ -9060,8 +9060,8 @@ namespace ts {
switch (source.kind) {
case SyntaxKind.Identifier:
return target.kind === SyntaxKind.Identifier && getResolvedSymbol(<Identifier>source) === getResolvedSymbol(<Identifier>target) ||
(target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(<Identifier>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(<BindingElement>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(<VariableDeclaration | BindingElement | ParameterDeclaration>node) :
getAssignedType(<Expression>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(<FlowCondition>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(<UnionType>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

View File

@@ -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