mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-07 23:08:20 -06:00
Narrow type in case/default sections in switch on discriminant property
This commit is contained in:
parent
4a8f94a553
commit
ce156460eb
@ -693,8 +693,23 @@ namespace ts {
|
||||
setFlowNodeReferenced(antecedent);
|
||||
return <FlowCondition>{
|
||||
flags,
|
||||
antecedent,
|
||||
expression,
|
||||
antecedent
|
||||
};
|
||||
}
|
||||
|
||||
function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode {
|
||||
const expr = switchStatement.expression;
|
||||
if (expr.kind !== SyntaxKind.PropertyAccessExpression || !isNarrowableReference((<PropertyAccessExpression>expr).expression)) {
|
||||
return antecedent;
|
||||
}
|
||||
setFlowNodeReferenced(antecedent);
|
||||
return <FlowSwitchClause>{
|
||||
flags: FlowFlags.SwitchClause,
|
||||
switchStatement,
|
||||
clauseStart,
|
||||
clauseEnd,
|
||||
antecedent
|
||||
};
|
||||
}
|
||||
|
||||
@ -923,9 +938,9 @@ namespace ts {
|
||||
preSwitchCaseFlow = currentFlow;
|
||||
bind(node.caseBlock);
|
||||
addAntecedent(postSwitchLabel, currentFlow);
|
||||
const hasNonEmptyDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause && c.statements.length);
|
||||
if (!hasNonEmptyDefault) {
|
||||
addAntecedent(postSwitchLabel, preSwitchCaseFlow);
|
||||
const hasDefault = forEach(node.caseBlock.clauses, c => c.kind === SyntaxKind.DefaultClause);
|
||||
if (!hasDefault) {
|
||||
addAntecedent(postSwitchLabel, createFlowSwitchClause(preSwitchCaseFlow, node, 0, 0));
|
||||
}
|
||||
currentBreakTarget = saveBreakTarget;
|
||||
preSwitchCaseFlow = savePreSwitchCaseFlow;
|
||||
@ -934,25 +949,22 @@ namespace ts {
|
||||
|
||||
function bindCaseBlock(node: CaseBlock): void {
|
||||
const clauses = node.clauses;
|
||||
let fallthroughFlow = unreachableFlow;
|
||||
for (let i = 0; i < clauses.length; i++) {
|
||||
const clause = clauses[i];
|
||||
if (clause.statements.length) {
|
||||
if (currentFlow.flags & FlowFlags.Unreachable) {
|
||||
currentFlow = preSwitchCaseFlow;
|
||||
}
|
||||
else {
|
||||
const preCaseLabel = createBranchLabel();
|
||||
addAntecedent(preCaseLabel, preSwitchCaseFlow);
|
||||
addAntecedent(preCaseLabel, currentFlow);
|
||||
currentFlow = finishFlowLabel(preCaseLabel);
|
||||
}
|
||||
bind(clause);
|
||||
if (!(currentFlow.flags & FlowFlags.Unreachable) && i !== clauses.length - 1 && options.noFallthroughCasesInSwitch) {
|
||||
errorOnFirstToken(clause, Diagnostics.Fallthrough_case_in_switch);
|
||||
}
|
||||
const clauseStart = i;
|
||||
while (!clauses[i].statements.length && i + 1 < clauses.length) {
|
||||
bind(clauses[i]);
|
||||
i++;
|
||||
}
|
||||
else {
|
||||
bind(clause);
|
||||
const preCaseLabel = createBranchLabel();
|
||||
addAntecedent(preCaseLabel, createFlowSwitchClause(preSwitchCaseFlow, <SwitchStatement>node.parent, clauseStart, i + 1));
|
||||
addAntecedent(preCaseLabel, fallthroughFlow);
|
||||
currentFlow = finishFlowLabel(preCaseLabel);
|
||||
const clause = clauses[i];
|
||||
bind(clause);
|
||||
fallthroughFlow = currentFlow;
|
||||
if (!(currentFlow.flags & FlowFlags.Unreachable) && i !== clauses.length - 1 && options.noFallthroughCasesInSwitch) {
|
||||
errorOnFirstToken(clause, Diagnostics.Fallthrough_case_in_switch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7676,6 +7676,29 @@ namespace ts {
|
||||
return node;
|
||||
}
|
||||
|
||||
function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) {
|
||||
if (clause.kind === SyntaxKind.CaseClause) {
|
||||
const expr = (<CaseClause>clause).expression;
|
||||
return expr.kind === SyntaxKind.StringLiteral ? getStringLiteralTypeForText((<StringLiteral>expr).text) : checkExpression(expr);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getSwitchClauseTypes(switchStatement: SwitchStatement): Type[] {
|
||||
const links = getNodeLinks(switchStatement);
|
||||
if (!links.switchTypes) {
|
||||
// If all case clauses specify expressions that have unit types, we return an array
|
||||
// of those unit types. Otherwise we return an empty array.
|
||||
const types = map(switchStatement.caseBlock.clauses, getTypeOfSwitchClause);
|
||||
links.switchTypes = forEach(types, t => !t || t.flags & TypeFlags.StringLiteral) ? types : emptyArray;
|
||||
}
|
||||
return links.switchTypes;
|
||||
}
|
||||
|
||||
function eachTypeContainedIn(source: Type, types: Type[]) {
|
||||
return source.flags & TypeFlags.Union ? !forEach((<UnionType>source).types, t => !contains(types, t)) : contains(types, source);
|
||||
}
|
||||
|
||||
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) {
|
||||
let key: string;
|
||||
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
|
||||
@ -7713,6 +7736,9 @@ namespace ts {
|
||||
else if (flow.flags & FlowFlags.Condition) {
|
||||
type = getTypeAtFlowCondition(<FlowCondition>flow);
|
||||
}
|
||||
else if (flow.flags & FlowFlags.SwitchClause) {
|
||||
type = getTypeAtSwitchClause(<FlowSwitchClause>flow);
|
||||
}
|
||||
else if (flow.flags & FlowFlags.Label) {
|
||||
if ((<FlowLabel>flow).antecedents.length === 1) {
|
||||
flow = (<FlowLabel>flow).antecedents[0];
|
||||
@ -7796,6 +7822,11 @@ namespace ts {
|
||||
return type;
|
||||
}
|
||||
|
||||
function getTypeAtSwitchClause(flow: FlowSwitchClause) {
|
||||
const type = getTypeAtFlowNode(flow.antecedent);
|
||||
return narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
|
||||
}
|
||||
|
||||
function getTypeAtFlowBranchLabel(flow: FlowLabel) {
|
||||
const antecedentTypes: Type[] = [];
|
||||
for (const antecedent of flow.antecedents) {
|
||||
@ -7938,6 +7969,33 @@ namespace ts {
|
||||
return type;
|
||||
}
|
||||
|
||||
function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
|
||||
// We have switch statement with property access expression
|
||||
if (!(type.flags & TypeFlags.Union) || !isMatchingReference(reference, (<PropertyAccessExpression>switchStatement.expression).expression)) {
|
||||
return type;
|
||||
}
|
||||
const propName = (<PropertyAccessExpression>switchStatement.expression).name.text;
|
||||
const propType = getTypeOfPropertyOfType(type, propName);
|
||||
if (!propType || !isStringLiteralUnionType(propType)) {
|
||||
return type;
|
||||
}
|
||||
const switchTypes = getSwitchClauseTypes(switchStatement);
|
||||
if (!switchTypes.length) {
|
||||
return type;
|
||||
}
|
||||
const types = (<UnionType>type).types;
|
||||
const clauseTypes = switchTypes.slice(clauseStart, clauseEnd);
|
||||
const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, undefined);
|
||||
const caseTypes = hasDefaultClause ? filter(clauseTypes, t => !!t) : clauseTypes;
|
||||
const discriminantType = caseTypes.length ? getUnionType(caseTypes) : undefined;
|
||||
const caseType = discriminantType && getUnionType(filter(types, t => isTypeComparableTo(discriminantType, getTypeOfPropertyOfType(t, propName))));
|
||||
if (!hasDefaultClause) {
|
||||
return caseType;
|
||||
}
|
||||
const defaultType = getUnionType(filter(types, t => !eachTypeContainedIn(getTypeOfPropertyOfType(t, propName), switchTypes)));
|
||||
return caseType ? getUnionType([caseType, defaultType]) : defaultType;
|
||||
}
|
||||
|
||||
function narrowTypeByTypeof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
|
||||
// We have '==', '!=', '====', or !==' operator with 'typeof xxx' on the left
|
||||
// and string literal on the right
|
||||
|
||||
@ -1554,8 +1554,9 @@ namespace ts {
|
||||
Assignment = 1 << 4, // Assignment
|
||||
TrueCondition = 1 << 5, // Condition known to be true
|
||||
FalseCondition = 1 << 6, // Condition known to be false
|
||||
Referenced = 1 << 7, // Referenced as antecedent once
|
||||
Shared = 1 << 8, // Referenced as antecedent more than once
|
||||
SwitchClause = 1 << 7, // Switch statement clause
|
||||
Referenced = 1 << 8, // Referenced as antecedent once
|
||||
Shared = 1 << 9, // Referenced as antecedent more than once
|
||||
Label = BranchLabel | LoopLabel,
|
||||
Condition = TrueCondition | FalseCondition
|
||||
}
|
||||
@ -1591,6 +1592,13 @@ namespace ts {
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
export interface FlowSwitchClause extends FlowNode {
|
||||
switchStatement: SwitchStatement;
|
||||
clauseStart: number; // Start index of case/default clause range
|
||||
clauseEnd: number; // End index of case/default clause range
|
||||
antecedent: FlowNode;
|
||||
}
|
||||
|
||||
export interface AmdDependency {
|
||||
path: string;
|
||||
name: string;
|
||||
@ -2170,6 +2178,7 @@ namespace ts {
|
||||
resolvedJsxType?: Type; // resolved element attributes type of a JSX openinglike element
|
||||
hasSuperCall?: boolean; // recorded result when we try to find super-call. We only try to find one if this flag is undefined, indicating that we haven't made an attempt.
|
||||
superCall?: ExpressionStatement; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing
|
||||
switchTypes?: Type[]; // Cached array of switch case expression types
|
||||
}
|
||||
|
||||
export const enum TypeFlags {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user