catchClauseMatches method

bool catchClauseMatches(
  1. SCatchClause clause,
  2. Object? thrownValue
)

Whether clause handles thrownValue.

SCD41: extracted so the ASYNC path can ask the same question. It used to take catchClauses.first regardless of type, so on StateError catch ran for an ArgumentError and a clause that must not match caught anyway — the same script behaving differently depending only on whether the enclosing function is async. Two implementations of catch matching is how that divergence arose; there is now one.

The WRAPPER is passed, not an unwrapped native view: _valueHasType does its own unwrapping where the host is needs it, and its bridged path prefers the wrapper's own bridge.

Implementation

bool catchClauseMatches(SCatchClause clause, Object? thrownValue) {
  // No `on Type`: matches anything.
  if (clause.exceptionType == null) return true;

  final typeNode = clause.exceptionType!;
  final targetCatchTypeName = typeNode is SNamedType
      ? typeNode.name!.name
      : '$typeNode';

  // SCC20: `on T` asks exactly the question `x is T` asks, so it asks it
  // through the same predicate. This was once a flat switch over sixteen
  // hardcoded type names — a SMALLER predicate than [_valueHasType] — and the
  // difference was measurable: `on Exception` missed a script class that
  // implements `Exception`, `on List<int>` caught a `List<String>`,
  // `on int Function(int)` was rejected outright, and a prefixed
  // `on c.HashSet` never resolved.
  try {
    return _valueHasType(typeNode, thrownValue);
  } on InternalInterpreterD4rtException {
    // SCE127: an `on T` naming something that does not resolve to a type is
    // a defect in the program text, and REAL DART REFUSES TO COMPILE IT
    // (`non_type_in_catch_clause`). d4rt used to log a warning and answer
    // `false`, which is the most dangerous available divergence: the clause
    // is inert, a LATER clause takes the branch, and the script returns a
    // value from a handler its author never meant to reach. With no later
    // clause the exception escaped the `try` as if the handler were not
    // written. Nothing in the output said so — a script author could only
    // discover a dead clause by testing that it fires, which is exactly the
    // test people skip, because catching by name is assumed to work.
    //
    // THE OLD COMMENT'S CONCERN IS REAL AND IS ANSWERED RATHER THAN
    // DISCARDED: "letting the lookup failure escape would replace the
    // exception being dispatched and lose the original". So the failure does
    // not escape — a diagnostic is raised that names BOTH the unresolved
    // type and the exception that was being dispatched when the dead clause
    // was reached, so neither half is lost.
    //
    // WHEN, and why here: Dart checks at compile time over the whole
    // program; d4rt resolves names lazily, so the analogue is to fail when
    // the clause is REACHED. That is later than Dart and only fires on an
    // exception that actually arrives — but it needs no new pass, and it
    // converts the silent wrong branch into a loud failure at the exact
    // moment the wrong branch would have been taken.
    //
    // MEASURED BEFORE CHANGING IT, because a legitimate unresolvable `on`
    // type would make working scripts start throwing: a class declared after
    // `main`, a class from another module, a prefixed `p.Other`, a generic
    // `List<int>` and a `typedef` alias all resolve. The one shape that does
    // not is a type from a library the script did not import — which Dart
    // also rejects, so it is this same defect rather than an exception to it.
    //
    // `thrownValue`, NOT the lookup failure. The first draft reported
    // `e.originalThrownValue` here, which is the resolution error itself —
    // so the diagnostic ended "the exception being dispatched was: Undefined
    // variable: X", naming the dead clause's own type back at the reader
    // instead of the exception in flight. That is the very loss the old
    // `return false` existed to prevent, reintroduced by the fix meant to
    // preserve it. SCC20's F-SCC20-16 caught it.
    throw RuntimeD4rtException.resolutionFailure(
      "The `on` clause names '$targetCatchTypeName', which does not resolve "
      "to a type, so it can never match and the exception would be handled "
      "somewhere else or not at all. Real Dart rejects this program "
      "(non_type_in_catch_clause). Import the library that declares "
      "'$targetCatchTypeName', or register a bridge for it. The exception "
      "being dispatched when the dead clause was reached was: $thrownValue",
    );
  } on UnimplementedD4rtException catch (e) {
    Logger.warn(
      "[STryStatement] Unsupported catch clause type node "
      "${typeNode.runtimeType}: ${e.message}",
    );
    return false;
  }
}