visitSwitchStatement method

  1. @override
Object? visitSwitchStatement(
  1. SSwitchStatement node
)
override

Visit a SSwitchStatement.

Implementation

@override
Object? visitSwitchStatement(SSwitchStatement node) {
  final switchValue = node.expression!.accept<Object?>(this);
  final switchEnvironment = Environment(enclosing: environment);
  final previousEnvironment = environment;
  environment = switchEnvironment;

  // Build a map of labels to member indices for 'continue <label>'
  final Map<String, int> labelToIndex = {};
  for (int i = 0; i < node.members.length; i++) {
    final member = node.members[i];
    List<dynamic> labels = [];
    if (member is SSwitchCase) {
      labels = member.labels;
    } else if (member is SSwitchDefault) {
      labels = member.labels;
    } else if (member is SSwitchPatternCase) {
      labels = member.labels;
    }
    for (final label in labels) {
      final labelName = (label is SLabel)
          ? label.label?.name
          : (label as SAstNode).toString();
      if (labelName != null) labelToIndex[labelName] = i;
    }
  }

  bool matched = false; // Has any case matched the switchValue?
  bool execute =
      false; // Should we execute statements in the current/next section?
  int startIndex = 0; // Starting index for processing members

  try {
    while (true) {
      // outer loop for continue with label
      try {
        for (int i = startIndex; i < node.members.length; i++) {
          final member = node.members[i];
          List<SAstNode> statementsToExecute = [];
          // Cluster C2: Dart 3 pattern cases do not fall through. When a
          // SwitchPatternCase with a constant pattern matches and runs,
          // we must break out of the for loop after executing — otherwise
          // subsequent cases would run with `execute` still true,
          // re-evaluating their bodies (e.g. reassigning `late final`
          // locals → "already been assigned").
          bool patternCaseMatchedThisIteration = false;

          if (member is SSwitchCase) {
            if (!matched) {
              final caseValue = member.expression!.accept<Object?>(this);
              Logger.debug(
                "[Switch] Checking legacy case value: $caseValue against $switchValue",
              );
              // Cluster-26: see _matchAndBind SConstantPattern — the
              // native enum / BridgedEnumValue boundary is asymmetric.
              if (switchValue == caseValue ||
                  (caseValue != null && caseValue == switchValue)) {
                matched = true;
                execute = true;
                Logger.debug("[Switch] Matched legacy case: $caseValue");
              }
            }
            statementsToExecute = member.statements;
          } else if (member is SSwitchPatternCase) {
            // Try explicit cast to potentially help the linter
            final gp = member.guardedPattern;
            final pattern = gp.pattern;
            if (pattern is SConstantPattern) {
              // This handles 'case <constant>:'
              if (!matched) {
                // Access expression from the SConstantPattern
                final caseValue = pattern.expression.accept<Object?>(this);
                Logger.debug(
                  "[Switch] Checking pattern case value: $caseValue against $switchValue",
                );
                // Cluster-26: see _matchAndBind SConstantPattern — the
                // native enum / BridgedEnumValue boundary is asymmetric.
                if (switchValue == caseValue ||
                    (caseValue != null && caseValue == switchValue)) {
                  matched = true;
                  execute = true; // Start executing
                  patternCaseMatchedThisIteration = true;
                  Logger.debug("[Switch] Matched pattern case: $caseValue");
                }
              }
              statementsToExecute = member.statements;
            } else {
              // Handle other pattern types using our improved _matchAndBind function
              if (!matched) {
                // Create a temporary environment for pattern matching in switch
                final tempEnvironment = Environment(enclosing: environment);
                try {
                  _matchAndBind(pattern, switchValue, tempEnvironment);
                  // If we get here, the pattern matched
                  matched = true;
                  // G-DOV-8 FIX: Execute pattern case body in the pattern's own scope
                  // and do NOT fall through to subsequent cases (Dart 3 semantics).
                  Logger.debug(
                    "[Switch] Matched pattern case: ${pattern.runtimeType}",
                  );

                  // Check guard clause (when)
                  final gpWhenClause = gp.whenClause;
                  if (gpWhenClause != null) {
                    final prevEnv = environment;
                    environment = tempEnvironment;
                    try {
                      final guardResult = gpWhenClause.expression
                          .accept<Object?>(this);
                      if (guardResult != true) {
                        environment = prevEnv;
                        // Guard failed, pattern doesn't match - reset matched so other cases can try
                        matched = false;
                        Logger.debug(
                          "[Switch] Guard clause failed, skipping case",
                        );
                        statementsToExecute = member.statements;
                        continue; // Skip to next case member
                      }
                    } catch (e) {
                      environment = prevEnv;
                      rethrow;
                    }
                    environment = prevEnv;
                  }

                  // Execute statements in the pattern environment
                  final prevEnv = environment;
                  environment = tempEnvironment;
                  try {
                    for (final statement in member.statements) {
                      statement.accept<Object?>(this);
                    }
                  } on BreakException catch (e) {
                    environment = prevEnv;
                    if (e.label == null ||
                        _currentStatementLabels.contains(e.label)) {
                      break; // Exit the loop over members
                    } else {
                      rethrow;
                    }
                  } on ContinueException catch (e) {
                    environment = prevEnv;
                    if (e.label != null &&
                        labelToIndex.containsKey(e.label)) {
                      startIndex = labelToIndex[e.label]!;
                      matched = true;
                      execute = true;
                      throw ContinueSwitchLabel();
                    } else {
                      rethrow;
                    }
                  } catch (e) {
                    environment = prevEnv;
                    rethrow;
                  }
                  environment = prevEnv;
                  // Pattern cases do not fall through - break out
                  break;
                } on PatternMatchD4rtException catch (e) {
                  Logger.debug(
                    "[Switch] Pattern ${pattern.runtimeType} did not match: ${e.message}",
                  );
                  // Pattern didn't match, continue to next case
                }
              }
              statementsToExecute = member.statements;
            }
          } else if (member is SSwitchDefault) {
            Logger.debug("[Switch] Reached default case.");
            if (!matched || execute) {
              // Execute default if no match OR fallthrough
              execute = true;
            }
            statementsToExecute = member.statements;
          } else {
            throw StateD4rtException(
              'Unknown switch member type: ${member.runtimeType}',
            );
          }

          // Execute statements if needed (either matched this round or fell through)
          if (execute) {
            Logger.debug(
              "[Switch] Executing statements for matched/fallthrough/default...",
            );
            try {
              for (final statement in statementsToExecute) {
                statement.accept<Object?>(this);
              }
              // Fall-through continues if no break
            } on BreakException catch (e) {
              Logger.debug(
                "[Switch] Caught BreakException (label: ${e.label}) with current labels: $_currentStatementLabels",
              );
              if (e.label == null ||
                  _currentStatementLabels.contains(e.label)) {
                // Unlabeled break OR labeled break targeting this switch.
                Logger.debug("[Switch] Breaking switch.");
                execute = false; // Stop execution after this block
                break; // Exit the loop over members
              } else {
                // Labeled break targeting an outer construct.
                Logger.debug("[Switch] Rethrowing outer break...");
                rethrow;
              }
            } on ContinueException catch (e) {
              if (e.label != null && labelToIndex.containsKey(e.label)) {
                // Continue to a labeled case in this switch
                Logger.debug("[Switch] Continue to labeled case: ${e.label}");
                startIndex = labelToIndex[e.label]!;
                matched =
                    true; // Mark as matched so we execute the target case
                execute = true;
                throw ContinueSwitchLabel(); // Signal to restart from target index
              } else {
                // 'continue' without a label or with unknown label
                throw RuntimeD4rtException(
                  "'continue' is not valid inside a switch case/default block without a loop target.",
                );
              }
            }
          }
          // Cluster C2: Dart 3 SwitchPatternCase with a constant pattern
          // does not fall through. After running its body, exit the
          // member loop so subsequent cases are not re-executed.
          //
          // Cluster C12: …but only break when the matching case actually
          // had statements. Dart 3 multi-case grouping —
          //   case A:
          //   case B:
          //     stmt;
          // — is parsed as two SSwitchPatternCase nodes: A with empty
          // statements and B with the body. The match fires on A, then
          // we must fall through to B so the body runs. Breaking out on
          // A would skip the body and silently return null from the
          // enclosing function (see foundation/targetplatform_test.dart's
          // switchFor helper).
          if (patternCaseMatchedThisIteration &&
              statementsToExecute.isNotEmpty) {
            execute = false;
            break;
          }
        }
        // Normal completion - exit the while(true) loop
        break;
      } on ContinueSwitchLabel {
        // Restart execution from startIndex (already set before throwing)
        continue;
      }
    }
  } finally {
    environment = previousEnvironment;
  }

  return null; // Switch statements don't produce a value.
}