code: @CheckReturnValue
    private FlowScope traverseCatch(Node catchNode, FlowScope scope) {
      Node catchTarget = catchNode.getFirstChild();
      if (catchTarget.isName()) {
        // TODO(lharker): is this case even necessary? seems like TypedScopeCreator handles it
        Node name = catchNode.getFirstChild();
        JSType type;
        // If the catch expression name was declared in the catch use that type,
        // otherwise use "unknown".
        JSDocInfo info = name.getJSDocInfo();
        if (info != null && info.hasType()) {
          type = info.getType().evaluate(scope.getDeclarationScope(), registry);
        } else {
          type = getNativeType(JSTypeNative.UNKNOWN_TYPE);
        }
        name.setJSType(type);
        return redeclareSimpleVar(scope, name, type);
      } else if (catchTarget.isDestructuringPattern()) {
        Node pattern = catchNode.getFirstChild();
        return traverseDestructuringPattern(pattern, scope, unknownType, AssignmentType.DECLARATION);
      } else {
        checkState(catchTarget.isEmpty(), catchTarget);
        // ES2019 allows `try {} catch {}` with no catch expression
        return scope;
      }
    }
docs: Any value can be thrown, so it's really impossible to determine the type of a CATCH param.
  Treat it as the UNKNOWN type.