docs: public String genObjToPass(CallNode callNode) {
      TranslateToPyExprVisitor translator =
          new TranslateToPyExprVisitor(localVarStack, pluginValueFactory, errorReporter);
  
      // Generate the expression for the original data to pass.
      String dataToPass;
      if (callNode.isPassingAllData()) {
        dataToPass = "data";
      } else if (callNode.isPassingData()) {
        dataToPass = translator.exec(callNode.getDataExpr()).getText();
      } else {
        dataToPass = "{}";
      }
  
      // Build an object literal containing the additional params.
      Map<PyExpr, PyExpr> additionalParams = new LinkedHashMap<>();
  
      for (CallParamNode child : callNode.getChildren()) {
        PyExpr key = new PyStringExpr("'" + child.getKey().identifier() + "'");
  
        if (child instanceof CallParamValueNode) {
          CallParamValueNode cpvn = (CallParamValueNode) child;
          additionalParams.put(key, translator.exec(cpvn.getExpr()));
        } else {
          CallParamContentNode cpcn = (CallParamContentNode) child;
          PyExpr valuePyExpr;
          if (isComputableAsPyExprVisitor.exec(cpcn)) {
            valuePyExpr =
                PyExprUtils.concatPyExprs(
                    genPyExprsVisitorFactory.create(localVarStack, errorReporter).exec(cpcn));
          } else {
            // This is a param with content that cannot be represented as Python expressions, so we
            // assume that code has been generated to define the temporary variable 'param<n>'.
            String paramExpr = "param" + cpcn.getId();
            // The param can be assumed to be a list at this point since it was created as an output
            // variable.
            valuePyExpr = new PyListExpr(paramExpr, Integer.MAX_VALUE);
          }
  
          // Param content nodes require a content kind in strict autoescaping, so the content must be
          // wrapped as SanitizedContent.
          valuePyExpr =
              InternalPyExprUtils.wrapAsSanitizedContent(
                  cpcn.getContentKind(), valuePyExpr.toPyString());
  
          additionalParams.put(key, valuePyExpr);
        }
      }
  
      Map<PyExpr, PyExpr> defaultParams = new LinkedHashMap<>();
      for (TemplateParam param : callNode.getNearestAncestor(TemplateNode.class).getParams()) {
        if (param.hasDefault()) {
          defaultParams.put(
              new PyStringExpr("'" + param.name() + "'"), translator.exec(param.defaultValue()));
        }
      }
  
      PyExpr additionalParamsExpr = PyExprUtils.convertMapToPyExpr(additionalParams);
  
      // Cases 2 and 3: Additional params with and without original data to pass.
      if (callNode.isPassingData()) {
        if (callNode.numChildren() > 0) {
          // make a shallow copy so we don't accidentally modify the param
          dataToPass = "dict(" + dataToPass + ")";
          dataToPass =
              "runtime.merge_into_dict(" + dataToPass + ", " + additionalParamsExpr.getText() + ")";
        }
  
        if (!defaultParams.isEmpty()) {
          // If there are default parameters, merge the data we have so far into a dict of the default
          // parameters. This will override the parameter defaults with actual values (if there are
          // actual values).
          PyExpr defaultParamsExpr = PyExprUtils.convertMapToPyExpr(defaultParams);
          dataToPass =
              "runtime.merge_into_dict(" + defaultParamsExpr.getText() + ", " + dataToPass + ")";
        }
  
        return dataToPass;
      } else {
        return additionalParamsExpr.getText();
      }
    }