@@ -578,20 +578,14 @@ namespace ts {
578578 }
579579 }
580580
581- function isNarrowableReference ( expr : Expression ) : boolean {
582- return expr . kind === SyntaxKind . Identifier ||
583- expr . kind === SyntaxKind . ThisKeyword ||
584- expr . kind === SyntaxKind . PropertyAccessExpression && isNarrowableReference ( ( < PropertyAccessExpression > expr ) . expression ) ;
585- }
586-
587581 function isNarrowingExpression ( expr : Expression ) : boolean {
588582 switch ( expr . kind ) {
589583 case SyntaxKind . Identifier :
590584 case SyntaxKind . ThisKeyword :
591585 case SyntaxKind . PropertyAccessExpression :
592586 return isNarrowableReference ( expr ) ;
593587 case SyntaxKind . CallExpression :
594- return true ;
588+ return hasNarrowableArgument ( < CallExpression > expr ) ;
595589 case SyntaxKind . ParenthesizedExpression :
596590 return isNarrowingExpression ( ( < ParenthesizedExpression > expr ) . expression ) ;
597591 case SyntaxKind . BinaryExpression :
@@ -602,6 +596,39 @@ namespace ts {
602596 return false ;
603597 }
604598
599+ function isNarrowableReference ( expr : Expression ) : boolean {
600+ return expr . kind === SyntaxKind . Identifier ||
601+ expr . kind === SyntaxKind . ThisKeyword ||
602+ expr . kind === SyntaxKind . PropertyAccessExpression && isNarrowableReference ( ( < PropertyAccessExpression > expr ) . expression ) ;
603+ }
604+
605+ function hasNarrowableArgument ( expr : CallExpression ) {
606+ if ( expr . arguments ) {
607+ for ( const argument of expr . arguments ) {
608+ if ( isNarrowableReference ( argument ) ) {
609+ return true ;
610+ }
611+ }
612+ }
613+ if ( expr . expression . kind === SyntaxKind . PropertyAccessExpression &&
614+ isNarrowableReference ( ( < PropertyAccessExpression > expr . expression ) . expression ) ) {
615+ return true ;
616+ }
617+ return false ;
618+ }
619+
620+ function isNarrowingNullCheckOperands ( expr1 : Expression , expr2 : Expression ) {
621+ return ( expr1 . kind === SyntaxKind . NullKeyword || expr1 . kind === SyntaxKind . Identifier && ( < Identifier > expr1 ) . text === "undefined" ) && isNarrowableOperand ( expr2 ) ;
622+ }
623+
624+ function isNarrowingTypeofOperands ( expr1 : Expression , expr2 : Expression ) {
625+ return expr1 . kind === SyntaxKind . TypeOfExpression && isNarrowableOperand ( ( < TypeOfExpression > expr1 ) . expression ) && expr2 . kind === SyntaxKind . StringLiteral ;
626+ }
627+
628+ function isNarrowingDiscriminant ( expr : Expression ) {
629+ return expr . kind === SyntaxKind . PropertyAccessExpression && isNarrowableReference ( ( < PropertyAccessExpression > expr ) . expression ) ;
630+ }
631+
605632 function isNarrowingBinaryExpression ( expr : BinaryExpression ) {
606633 switch ( expr . operatorToken . kind ) {
607634 case SyntaxKind . EqualsToken :
@@ -610,34 +637,35 @@ namespace ts {
610637 case SyntaxKind . ExclamationEqualsToken :
611638 case SyntaxKind . EqualsEqualsEqualsToken :
612639 case SyntaxKind . ExclamationEqualsEqualsToken :
613- if ( ( isNarrowingExpression ( expr . left ) && ( expr . right . kind === SyntaxKind . NullKeyword || expr . right . kind === SyntaxKind . Identifier ) ) ||
614- ( isNarrowingExpression ( expr . right ) && ( expr . left . kind === SyntaxKind . NullKeyword || expr . left . kind === SyntaxKind . Identifier ) ) ) {
615- return true ;
616- }
617- if ( isTypeOfNarrowingBinaryExpression ( expr ) ) {
618- return true ;
619- }
620- return false ;
640+ return isNarrowingNullCheckOperands ( expr . right , expr . left ) || isNarrowingNullCheckOperands ( expr . left , expr . right ) ||
641+ isNarrowingTypeofOperands ( expr . right , expr . left ) || isNarrowingTypeofOperands ( expr . left , expr . right ) ||
642+ isNarrowingDiscriminant ( expr . left ) || isNarrowingDiscriminant ( expr . right ) ;
621643 case SyntaxKind . InstanceOfKeyword :
622- return isNarrowingExpression ( expr . left ) ;
644+ return isNarrowableOperand ( expr . left ) ;
623645 case SyntaxKind . CommaToken :
624646 return isNarrowingExpression ( expr . right ) ;
625647 }
626648 return false ;
627649 }
628650
629- function isTypeOfNarrowingBinaryExpression ( expr : BinaryExpression ) {
630- let typeOf : Expression ;
631- if ( expr . left . kind === SyntaxKind . StringLiteral ) {
632- typeOf = expr . right ;
633- }
634- else if ( expr . right . kind === SyntaxKind . StringLiteral ) {
635- typeOf = expr . left ;
636- }
637- else {
638- typeOf = undefined ;
651+ function isNarrowableOperand ( expr : Expression ) : boolean {
652+ switch ( expr . kind ) {
653+ case SyntaxKind . ParenthesizedExpression :
654+ return isNarrowableOperand ( ( < ParenthesizedExpression > expr ) . expression ) ;
655+ case SyntaxKind . BinaryExpression :
656+ switch ( ( < BinaryExpression > expr ) . operatorToken . kind ) {
657+ case SyntaxKind . EqualsToken :
658+ return isNarrowableOperand ( ( < BinaryExpression > expr ) . left ) ;
659+ case SyntaxKind . CommaToken :
660+ return isNarrowableOperand ( ( < BinaryExpression > expr ) . right ) ;
661+ }
639662 }
640- return typeOf && typeOf . kind === SyntaxKind . TypeOfExpression && isNarrowingExpression ( ( < TypeOfExpression > typeOf ) . expression ) ;
663+ return isNarrowableReference ( expr ) ;
664+ }
665+
666+ function isNarrowingSwitchStatement ( switchStatement : SwitchStatement ) {
667+ const expr = switchStatement . expression ;
668+ return expr . kind === SyntaxKind . PropertyAccessExpression && isNarrowableReference ( ( < PropertyAccessExpression > expr ) . expression ) ;
641669 }
642670
643671 function createBranchLabel ( ) : FlowLabel {
@@ -683,8 +711,22 @@ namespace ts {
683711 setFlowNodeReferenced ( antecedent ) ;
684712 return < FlowCondition > {
685713 flags,
686- antecedent,
687714 expression,
715+ antecedent
716+ } ;
717+ }
718+
719+ function createFlowSwitchClause ( antecedent : FlowNode , switchStatement : SwitchStatement , clauseStart : number , clauseEnd : number ) : FlowNode {
720+ if ( ! isNarrowingSwitchStatement ( switchStatement ) ) {
721+ return antecedent ;
722+ }
723+ setFlowNodeReferenced ( antecedent ) ;
724+ return < FlowSwitchClause > {
725+ flags : FlowFlags . SwitchClause ,
726+ switchStatement,
727+ clauseStart,
728+ clauseEnd,
729+ antecedent
688730 } ;
689731 }
690732
@@ -913,9 +955,12 @@ namespace ts {
913955 preSwitchCaseFlow = currentFlow ;
914956 bind ( node . caseBlock ) ;
915957 addAntecedent ( postSwitchLabel , currentFlow ) ;
916- const hasNonEmptyDefault = forEach ( node . caseBlock . clauses , c => c . kind === SyntaxKind . DefaultClause && c . statements . length ) ;
917- if ( ! hasNonEmptyDefault ) {
918- addAntecedent ( postSwitchLabel , preSwitchCaseFlow ) ;
958+ const hasDefault = forEach ( node . caseBlock . clauses , c => c . kind === SyntaxKind . DefaultClause ) ;
959+ // We mark a switch statement as possibly exhaustive if it has no default clause and if all
960+ // case clauses have unreachable end points (e.g. they all return).
961+ node . possiblyExhaustive = ! hasDefault && ! postSwitchLabel . antecedents ;
962+ if ( ! hasDefault ) {
963+ addAntecedent ( postSwitchLabel , createFlowSwitchClause ( preSwitchCaseFlow , node , 0 , 0 ) ) ;
919964 }
920965 currentBreakTarget = saveBreakTarget ;
921966 preSwitchCaseFlow = savePreSwitchCaseFlow ;
@@ -924,25 +969,22 @@ namespace ts {
924969
925970 function bindCaseBlock ( node : CaseBlock ) : void {
926971 const clauses = node . clauses ;
972+ let fallthroughFlow = unreachableFlow ;
927973 for ( let i = 0 ; i < clauses . length ; i ++ ) {
928- const clause = clauses [ i ] ;
929- if ( clause . statements . length ) {
930- if ( currentFlow . flags & FlowFlags . Unreachable ) {
931- currentFlow = preSwitchCaseFlow ;
932- }
933- else {
934- const preCaseLabel = createBranchLabel ( ) ;
935- addAntecedent ( preCaseLabel , preSwitchCaseFlow ) ;
936- addAntecedent ( preCaseLabel , currentFlow ) ;
937- currentFlow = finishFlowLabel ( preCaseLabel ) ;
938- }
939- bind ( clause ) ;
940- if ( ! ( currentFlow . flags & FlowFlags . Unreachable ) && i !== clauses . length - 1 && options . noFallthroughCasesInSwitch ) {
941- errorOnFirstToken ( clause , Diagnostics . Fallthrough_case_in_switch ) ;
942- }
974+ const clauseStart = i ;
975+ while ( ! clauses [ i ] . statements . length && i + 1 < clauses . length ) {
976+ bind ( clauses [ i ] ) ;
977+ i ++ ;
943978 }
944- else {
945- bind ( clause ) ;
979+ const preCaseLabel = createBranchLabel ( ) ;
980+ addAntecedent ( preCaseLabel , createFlowSwitchClause ( preSwitchCaseFlow , < SwitchStatement > node . parent , clauseStart , i + 1 ) ) ;
981+ addAntecedent ( preCaseLabel , fallthroughFlow ) ;
982+ currentFlow = finishFlowLabel ( preCaseLabel ) ;
983+ const clause = clauses [ i ] ;
984+ bind ( clause ) ;
985+ fallthroughFlow = currentFlow ;
986+ if ( ! ( currentFlow . flags & FlowFlags . Unreachable ) && i !== clauses . length - 1 && options . noFallthroughCasesInSwitch ) {
987+ errorOnFirstToken ( clause , Diagnostics . Fallthrough_case_in_switch ) ;
946988 }
947989 }
948990 }
0 commit comments