docs: public Expression genObjToPass(
        CallNode callNode,
        TemplateAliases templateAliases,
        TranslationContext translationContext,
        ErrorReporter errorReporter,
        TranslateExprNodeVisitor exprTranslator) {
  
      // ------ Generate the expression for the original data to pass ------
      Expression dataToPass;
      if (callNode.isPassingAllData()) {
        dataToPass = JsRuntime.OPT_DATA;
      } else if (callNode.isPassingData()) {
        dataToPass = exprTranslator.exec(callNode.getDataExpr());
      } else if (callNode.numChildren() == 0) {
        // If we're passing neither children nor indirect data, we can immediately return null.
        return LITERAL_NULL;
      } else {
        dataToPass = LITERAL_NULL;
      }
  
      Map<String, Expression> paramDefaults = getDefaultParams(callNode, translationContext);
      // ------ Case 1: No additional params ------
      if (callNode.numChildren() == 0) {
        if (!paramDefaults.isEmpty()) {
          dataToPass = SOY_ASSIGN_DEFAULTS.call(dataToPass, Expression.objectLiteral(paramDefaults));
        }
        // Ignore inconsistencies between Closure Compiler & Soy type systems (eg, proto nullability).
        return dataToPass.castAs("?");
      }
  
      // ------ Build an object literal containing the additional params ------
      Map<String, Expression> params = paramDefaults;
  
      for (CallParamNode child : callNode.getChildren()) {
        Expression value;
  
        if (child instanceof CallParamValueNode) {
          CallParamValueNode cpvn = (CallParamValueNode) child;
          value = exprTranslator.exec(cpvn.getExpr());
        } else {
          CallParamContentNode cpcn = (CallParamContentNode) child;
  
          if (isComputableAsJsExprsVisitor.exec(cpcn)) {
            List<Expression> chunks =
                genJsExprsVisitorFactory
                    .create(translationContext, templateAliases, errorReporter)
                    .exec(cpcn);
            value = CodeChunkUtils.concatChunksForceString(chunks);
          } else {
            // This is a param with content that cannot be represented as JS expressions, so we assume
            // that code has been generated to define the temporary variable 'param<n>'.
            value = id("param" + cpcn.getId());
          }
  
          value = maybeWrapContent(translationContext.codeGenerator(), cpcn, value);
        }
        params.put(child.getKey().identifier(), value);
      }
  
      Expression paramsExp = Expression.objectLiteral(params);
  
      // ------ Cases 2 and 3: Additional params with and without original data to pass ------
      if (callNode.isPassingData()) {
        Expression allData = SOY_ASSIGN_DEFAULTS.call(paramsExp, dataToPass);
        // No need to cast; assignDefaults already returns {?}.
        return allData;
      } else {
        // Ignore inconsistencies between Closure Compiler & Soy type systems (eg, proto nullability).
        return paramsExp.castAs("?");
      }
    }