@@ -59,7 +59,6 @@ type parser struct {
59
59
declaredSymbols []js_ast.DeclaredSymbol
60
60
globPatternImports []globPatternImport
61
61
runtimeImports map[string]ast.LocRef
62
- deadCaseChecker deadCaseChecker
63
62
duplicateCaseChecker duplicateCaseChecker
64
63
unrepresentableIdentifiers map[string]bool
65
64
legacyOctalLiterals map[js_ast.E]logger.Range
@@ -748,84 +747,87 @@ type fnOnlyDataVisit struct {
748
747
silenceMessageAboutThisBeingUndefined bool
749
748
}
750
749
751
- type livenessStatus uint8
750
+ type livenessStatus int8
752
751
753
752
const (
754
- livenessUnknown livenessStatus = iota
755
- alwaysDead
756
- alwaysLive
753
+ alwaysDead livenessStatus = -1
754
+ livenessUnknown livenessStatus = 0
755
+ alwaysLive livenessStatus = 1
757
756
)
758
757
759
- type deadCaseChecker struct {
760
- test js_ast.E
761
- earlierCaseWasMaybeTaken bool
762
- furtherCasesAreDead bool
763
- mayHaveFallenThrough bool
758
+ type switchCaseLiveness struct {
759
+ status livenessStatus
760
+ canFallThrough bool
764
761
}
765
762
766
- func (dc *deadCaseChecker) reset(p *parser, test js_ast.E) {
767
- *dc = deadCaseChecker{
768
- test: test,
769
- furtherCasesAreDead: p.isControlFlowDead,
770
- }
771
- }
772
-
773
- func (dc *deadCaseChecker) checkCase(c js_ast.Case) (status livenessStatus) {
774
- if dc.furtherCasesAreDead {
775
- return alwaysDead
776
- }
763
+ func analyzeSwitchCasesForLiveness(s *js_ast.SSwitch) []switchCaseLiveness {
764
+ cases := make([]switchCaseLiveness, 0, len(s.Cases))
765
+ defaultIndex := -1
766
+
767
+ // Determine the status of the individual cases independently
768
+ maxStatus := alwaysDead
769
+ for i, c := range s.Cases {
770
+ if c.ValueOrNil.Data == nil {
771
+ defaultIndex = i
772
+ }
773
+
774
+ // Check the value for strict equality
775
+ var status livenessStatus
776
+ if maxStatus == alwaysLive {
777
+ status = alwaysDead // Everything after an always-live case is always dead
778
+ } else if c.ValueOrNil.Data == nil {
779
+ status = alwaysDead // This is the default case, and will be filled in later
780
+ } else if isEqualToTest, ok := js_ast.CheckEqualityIfNoSideEffects(s.Test.Data, c.ValueOrNil.Data, js_ast.StrictEquality); ok {
781
+ if isEqualToTest {
782
+ status = alwaysLive // This branch will always be matched, and will be taken unless an earlier branch was taken
783
+ } else {
784
+ status = alwaysDead // This branch will never be matched, and will not be taken unless there was fall-through
785
+ }
786
+ } else {
787
+ status = livenessUnknown // This branch depends on run-time values and may or may not be matched
788
+ }
789
+ if maxStatus < status {
790
+ maxStatus = status
791
+ }
777
792
778
- // Check for strict equality
779
- var isEqualToTest bool
780
- var isEqualityKnown bool
781
- if c.ValueOrNil.Data != nil {
782
- // Non-default case
783
- isEqualToTest, isEqualityKnown = js_ast.CheckEqualityIfNoSideEffects(dc.test, c.ValueOrNil.Data, js_ast.StrictEquality)
784
- } else {
785
- // Default case
786
- if !dc.earlierCaseWasMaybeTaken {
787
- isEqualToTest = true
788
- isEqualityKnown = true
793
+ // Check for potential fall-through by checking for a jump at the end of the body
794
+ canFallThrough := true
795
+ stmts := c.Body
796
+ for len(stmts) > 0 {
797
+ switch s := stmts[len(stmts)-1].Data.(type) {
798
+ case *js_ast.SBlock:
799
+ stmts = s.Stmts // If this ends with a block, check the block's body next
800
+ continue
801
+ case *js_ast.SBreak, *js_ast.SContinue, *js_ast.SReturn, *js_ast.SThrow:
802
+ canFallThrough = false
803
+ }
804
+ break
789
805
}
806
+
807
+ cases = append(cases, switchCaseLiveness{
808
+ status: status,
809
+ canFallThrough: canFallThrough,
810
+ })
790
811
}
791
812
792
- // Check for potential fall-through by checking for a jump at the end of the body
793
- canFallThrough := true
794
- stmts := c.Body
795
- for len(stmts) > 0 {
796
- switch s := stmts[len(stmts)-1].Data.(type) {
797
- case *js_ast.SBlock:
798
- stmts = s.Stmts // If this ends with a block, check the block's body next
799
- continue
800
- case *js_ast.SBreak, *js_ast.SContinue, *js_ast.SReturn, *js_ast.SThrow:
801
- canFallThrough = false
802
- }
803
- break
813
+ // Set the liveness for the default case last based on the other cases
814
+ if defaultIndex != -1 {
815
+ // The negation here transposes "always live" with "always dead"
816
+ cases[defaultIndex].status = -maxStatus
804
817
}
805
818
806
- // Update the state machine
807
- if isEqualityKnown {
808
- if isEqualToTest {
809
- // This branch will always be matched, and will be taken unless an earlier branch was taken
810
- if !dc.earlierCaseWasMaybeTaken {
811
- status = alwaysLive
812
- }
813
- if !canFallThrough {
814
- dc.furtherCasesAreDead = true
815
- }
816
- dc.earlierCaseWasMaybeTaken = true
817
- } else {
818
- // This branch will never be matched, and will not be taken unless there was fall-through
819
- if !dc.mayHaveFallenThrough {
820
- status = alwaysDead
819
+ // Then propagate fall-through information in linear fall-through order
820
+ for i, c := range cases {
821
+ // Propagate state forward if this isn't dead. Note that the "can fall
822
+ // through" flag does not imply "must fall through". The body may have
823
+ // an embedded "break" inside an if statement, for example.
824
+ if c.status != alwaysDead {
825
+ for j := i + 1; j < len(cases) && cases[j-1].canFallThrough; j++ {
826
+ cases[j].status = livenessUnknown
821
827
}
822
828
}
823
- } else {
824
- // This branch depends on run-time values and may or may not be matched
825
- dc.earlierCaseWasMaybeTaken = true
826
829
}
827
- dc.mayHaveFallenThrough = canFallThrough && status != alwaysDead
828
- return
830
+ return cases
829
831
}
830
832
831
833
const bloomFilterSize = 251
@@ -10959,23 +10961,36 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_
10959
10961
p.pushScopeForVisitPass(js_ast.ScopeBlock, s.BodyLoc)
10960
10962
oldIsInsideSwitch := p.fnOrArrowDataVisit.isInsideSwitch
10961
10963
p.fnOrArrowDataVisit.isInsideSwitch = true
10962
- p.deadCaseChecker.reset(p, s.Test.Data)
10963
- end := 0
10964
- for _, c := range s.Cases {
10965
- // Visit the value for non-default cases
10966
- var status livenessStatus
10964
+
10965
+ // Visit case values first
10966
+ for i := range s.Cases {
10967
+ c := &s.Cases[i]
10967
10968
if c.ValueOrNil.Data != nil {
10968
10969
c.ValueOrNil = p.visitExpr(c.ValueOrNil)
10969
- status = p.deadCaseChecker.checkCase(c)
10970
10970
p.warnAboutEqualityCheck("case", c.ValueOrNil, c.ValueOrNil.Loc)
10971
10971
p.warnAboutTypeofAndString(s.Test, c.ValueOrNil, onlyCheckOriginalOrder)
10972
- } else {
10973
- status = p.deadCaseChecker.checkCase(c)
10974
10972
}
10973
+ }
10974
+
10975
+ // Check for duplicate case values
10976
+ p.duplicateCaseChecker.reset()
10977
+ for _, c := range s.Cases {
10978
+ if c.ValueOrNil.Data != nil {
10979
+ p.duplicateCaseChecker.check(p, c.ValueOrNil)
10980
+ }
10981
+ }
10982
+
10983
+ // Then analyze the cases to determine which ones are live and/or dead
10984
+ cases := analyzeSwitchCasesForLiveness(s)
10985
+
10986
+ // Then visit case bodies, and potentially filter out dead cases
10987
+ end := 0
10988
+ for i, c := range s.Cases {
10989
+ isAlwaysDead := cases[i].status == alwaysDead
10975
10990
10976
10991
// Potentially treat the case body as dead code
10977
10992
old := p.isControlFlowDead
10978
- if status == alwaysDead {
10993
+ if isAlwaysDead {
10979
10994
p.isControlFlowDead = true
10980
10995
}
10981
10996
c.Body = p.visitStmts(c.Body, stmtsSwitch)
@@ -10986,29 +11001,19 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_
10986
11001
// removed safely, so if the body isn't empty then that means it contains
10987
11002
// some statements that can't be removed safely (e.g. a hoisted "var").
10988
11003
// So don't remove this case if the body isn't empty.
10989
- if p.options.minifySyntax && status == alwaysDead && len(c.Body) == 0 {
11004
+ if p.options.minifySyntax && isAlwaysDead && len(c.Body) == 0 {
10990
11005
continue
10991
11006
}
10992
11007
10993
11008
// Make sure the assignment to the body above is preserved
10994
11009
s.Cases[end] = c
10995
11010
end++
10996
11011
}
10997
-
10998
- // Filter out all removed cases
10999
11012
s.Cases = s.Cases[:end]
11000
11013
11001
11014
p.fnOrArrowDataVisit.isInsideSwitch = oldIsInsideSwitch
11002
11015
p.popScope()
11003
11016
11004
- // Check for duplicate case values
11005
- p.duplicateCaseChecker.reset()
11006
- for _, c := range s.Cases {
11007
- if c.ValueOrNil.Data != nil {
11008
- p.duplicateCaseChecker.check(p, c.ValueOrNil)
11009
- }
11010
- }
11011
-
11012
11017
// Unwrap switch statements in dead code
11013
11018
if p.options.minifySyntax && p.isControlFlowDead {
11014
11019
for _, c := range s.Cases {
0 commit comments