Patch 8.2.4930
Problem:    Interpolated string expression requires escaping.
Solution:   Do not require escaping in the expression.
Files:      runtime/doc/eval.txt, src/typval.c, src/proto/typval.pro,
            src/dict.c, src/eval.c, src/evalvars.c, src/proto/evalvars.pro,
            src/vim9compile.c, src/proto/vim9compile.pro, src/vim9expr.c,
            src/vim9instr.c, src/alloc.c, src/proto/alloc.pro,
            src/testdir/test_expr.vim, src/testdir/test_let.vim


*** ../vim-8.2.4929/runtime/doc/eval.txt        2022-05-06 13:14:43.789076617 
+0100
--- runtime/doc/eval.txt        2022-05-10 11:11:12.243900263 +0100
***************
*** 3215,3234 ****
                        {endmarker}.
  
                        If "eval" is not specified, then each line of text is
!                       used as a |literal-string|.  If "eval" is specified,
!                       then any Vim expression in the form ``={expr}`` is
!                       evaluated and the result replaces the expression.
                        Example where $HOME is expanded: >
                                let lines =<< trim eval END
                                  some text
!                                 See the file `=$HOME`/.vimrc
                                  more text
                                END
  <                     There can be multiple Vim expressions in a single line
                        but an expression cannot span multiple lines.  If any
                        expression evaluation fails, then the assignment fails.
-                       once the "`=" has been found {expr} and a backtick
-                       must follow.  {expr} cannot be empty.
  
                        {endmarker} must not contain white space.
                        {endmarker} cannot start with a lower case character.
--- 3261,3280 ----
                        {endmarker}.
  
                        If "eval" is not specified, then each line of text is
!                       used as a |literal-string|, except that single quotes
!                       doe not need to be doubled.
!                       If "eval" is specified, then any Vim expression in the
!                       form {expr} is evaluated and the result replaces the
!                       expression, like with |interp-string|.
                        Example where $HOME is expanded: >
                                let lines =<< trim eval END
                                  some text
!                                 See the file {$HOME}/.vimrc
                                  more text
                                END
  <                     There can be multiple Vim expressions in a single line
                        but an expression cannot span multiple lines.  If any
                        expression evaluation fails, then the assignment fails.
  
                        {endmarker} must not contain white space.
                        {endmarker} cannot start with a lower case character.
*** ../vim-8.2.4929/src/typval.c        2022-05-09 20:09:19.294641425 +0100
--- src/typval.c        2022-05-10 12:47:12.209820875 +0100
***************
*** 2065,2083 ****
  }
  
  /*
!  * Allocate a variable for a string constant.
   * Return OK or FAIL.
   */
      int
! eval_string(char_u **arg, typval_T *rettv, int evaluate)
  {
      char_u    *p;
      char_u    *end;
!     int               extra = 0;
      int               len;
  
      // Find the end of the string, skipping backslashed characters.
!     for (p = *arg + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p))
      {
        if (*p == '\\' && p[1] != NUL)
        {
--- 2065,2087 ----
  }
  
  /*
!  * Evaluate a string constant and put the result in "rettv".
!  * "*arg" points to the double quote or to after it when "interpolate" is 
TRUE.
!  * When "interpolate" is TRUE reduce "{{" to "{", reduce "}}" to "}" and stop
!  * at a single "{".
   * Return OK or FAIL.
   */
      int
! eval_string(char_u **arg, typval_T *rettv, int evaluate, int interpolate)
  {
      char_u    *p;
      char_u    *end;
!     int               extra = interpolate ? 1 : 0;
!     int               off = interpolate ? 0 : 1;
      int               len;
  
      // Find the end of the string, skipping backslashed characters.
!     for (p = *arg + off; *p != NUL && *p != '"'; MB_PTR_ADV(p))
      {
        if (*p == '\\' && p[1] != NUL)
        {
***************
*** 2088,2096 ****
            if (*p == '<')
                extra += 5;
        }
      }
  
!     if (*p != '"')
      {
        semsg(_(e_missing_double_quote_str), *arg);
        return FAIL;
--- 2092,2112 ----
            if (*p == '<')
                extra += 5;
        }
+       else if (interpolate && (*p == '{' || *p == '}'))
+       {
+           if (*p == '{' && p[1] != '{') // start of expression
+               break;
+           ++p;
+           if (p[-1] == '}' && *p != '}') // single '}' is an error
+           {
+               semsg(_(e_stray_closing_curly_str), *arg);
+               return FAIL;
+           }
+           --extra;  // "{{" becomes "{", "}}" becomes "}"
+       }
      }
  
!     if (*p != '"' && !(interpolate && *p == '{'))
      {
        semsg(_(e_missing_double_quote_str), *arg);
        return FAIL;
***************
*** 2099,2105 ****
      // If only parsing, set *arg and return here
      if (!evaluate)
      {
!       *arg = p + 1;
        return OK;
      }
  
--- 2115,2121 ----
      // If only parsing, set *arg and return here
      if (!evaluate)
      {
!       *arg = p + off;
        return OK;
      }
  
***************
*** 2112,2118 ****
        return FAIL;
      end = rettv->vval.v_string;
  
!     for (p = *arg + 1; *p != NUL && *p != '"'; )
      {
        if (*p == '\\')
        {
--- 2128,2134 ----
        return FAIL;
      end = rettv->vval.v_string;
  
!     for (p = *arg + off; *p != NUL && *p != '"'; )
      {
        if (*p == '\\')
        {
***************
*** 2192,2206 ****
                          }
                          // FALLTHROUGH
  
!               default:  MB_COPY_CHAR(p, end);
                          break;
            }
        }
        else
            MB_COPY_CHAR(p, end);
      }
      *end = NUL;
!     if (*p != NUL) // just in case
        ++p;
      *arg = p;
  
--- 2208,2230 ----
                          }
                          // FALLTHROUGH
  
!               default: MB_COPY_CHAR(p, end);
                          break;
            }
        }
        else
+       {
+           if (interpolate && (*p == '{' || *p == '}'))
+           {
+               if (*p == '{' && p[1] != '{') // start of expression
+                   break;
+               ++p;  // reduce "{{" to "{" and "}}" to "}"
+           }
            MB_COPY_CHAR(p, end);
+       }
      }
      *end = NUL;
!     if (*p == '"' && !interpolate)
        ++p;
      *arg = p;
  
***************
*** 2209,2225 ****
  
  /*
   * Allocate a variable for a 'str''ing' constant.
!  * Return OK or FAIL.
   */
      int
! eval_lit_string(char_u **arg, typval_T *rettv, int evaluate)
  {
      char_u    *p;
      char_u    *str;
!     int               reduce = 0;
  
      // Find the end of the string, skipping ''.
!     for (p = *arg + 1; *p != NUL; MB_PTR_ADV(p))
      {
        if (*p == '\'')
        {
--- 2233,2252 ----
  
  /*
   * Allocate a variable for a 'str''ing' constant.
!  * When "interpolate" is TRUE reduce "{{" to "{" and stop at a single "{".
!  * Return OK when a "rettv" was set to the string.
!  * Return FAIL on error, "rettv" is not set.
   */
      int
! eval_lit_string(char_u **arg, typval_T *rettv, int evaluate, int interpolate)
  {
      char_u    *p;
      char_u    *str;
!     int               reduce = interpolate ? -1 : 0;
!     int               off = interpolate ? 0 : 1;
  
      // Find the end of the string, skipping ''.
!     for (p = *arg + off; *p != NUL; MB_PTR_ADV(p))
      {
        if (*p == '\'')
        {
***************
*** 2228,2236 ****
            ++reduce;
            ++p;
        }
      }
  
!     if (*p != '\'')
      {
        semsg(_(e_missing_single_quote_str), *arg);
        return FAIL;
--- 2255,2283 ----
            ++reduce;
            ++p;
        }
+       else if (interpolate)
+       {
+           if (*p == '{')
+           {
+               if (p[1] != '{')
+                   break;
+               ++p;
+               ++reduce;
+           }
+           else if (*p == '}')
+           {
+               ++p;
+               if (*p != '}')
+               {
+                   semsg(_(e_stray_closing_curly_str), *arg);
+                   return FAIL;
+               }
+               ++reduce;
+           }
+       }
      }
  
!     if (*p != '\'' && !(interpolate && *p == '{'))
      {
        semsg(_(e_missing_single_quote_str), *arg);
        return FAIL;
***************
*** 2239,2256 ****
      // If only parsing return after setting "*arg"
      if (!evaluate)
      {
!       *arg = p + 1;
        return OK;
      }
  
!     // Copy the string into allocated memory, handling '' to ' reduction.
      str = alloc((p - *arg) - reduce);
      if (str == NULL)
        return FAIL;
      rettv->v_type = VAR_STRING;
      rettv->vval.v_string = str;
  
!     for (p = *arg + 1; *p != NUL; )
      {
        if (*p == '\'')
        {
--- 2286,2304 ----
      // If only parsing return after setting "*arg"
      if (!evaluate)
      {
!       *arg = p + off;
        return OK;
      }
  
!     // Copy the string into allocated memory, handling '' to ' reduction and
!     // any expressions.
      str = alloc((p - *arg) - reduce);
      if (str == NULL)
        return FAIL;
      rettv->v_type = VAR_STRING;
      rettv->vval.v_string = str;
  
!     for (p = *arg + off; *p != NUL; )
      {
        if (*p == '\'')
        {
***************
*** 2258,2295 ****
                break;
            ++p;
        }
        MB_COPY_CHAR(p, str);
      }
      *str = NUL;
!     *arg = p + 1;
  
      return OK;
  }
  
      int
  eval_interp_string(char_u **arg, typval_T *rettv, int evaluate)
  {
      typval_T  tv;
!     int               ret;
! 
!     // *arg is on the '$' character.
!     (*arg)++;
  
!     rettv->v_type = VAR_STRING;
  
!     if (**arg == '"')
!       ret = eval_string(arg, &tv, evaluate);
!     else
!       ret = eval_lit_string(arg, &tv, evaluate);
  
!     if (ret == FAIL || !evaluate)
!       return ret;
  
!     rettv->vval.v_string = eval_all_expr_in_str(tv.vval.v_string);
  
!     clear_tv(&tv);
  
!     return rettv->vval.v_string != NULL ? OK : FAIL;
  }
  
  /*
--- 2306,2387 ----
                break;
            ++p;
        }
+       else if (interpolate && (*p == '{' || *p == '}'))
+       {
+           if (*p == '{' && p[1] != '{')
+               break;
+           ++p;
+       }
        MB_COPY_CHAR(p, str);
      }
      *str = NUL;
!     *arg = p + off;
  
      return OK;
  }
  
+ /*
+  * Evaluate a single or double quoted string possibly containing expressions.
+  * "arg" points to the '$'.  The result is put in "rettv".
+  * Returns OK or FAIL.
+  */
      int
  eval_interp_string(char_u **arg, typval_T *rettv, int evaluate)
  {
      typval_T  tv;
!     int               ret = OK;
!     int               quote;
!     garray_T  ga;
!     char_u    *p;
  
!     ga_init2(&ga, 1, 80);
  
!     // *arg is on the '$' character, move it to the first string character.
!     ++*arg;
!     quote = **arg;
!     ++*arg;
  
!     for (;;)
!     {
!       // Get the string up to the matching quote or to a single '{'.
!       // "arg" is advanced to either the quote or the '{'.
!       if (quote == '"')
!           ret = eval_string(arg, &tv, evaluate, TRUE);
!       else
!           ret = eval_lit_string(arg, &tv, evaluate, TRUE);
!       if (ret == FAIL)
!           break;
!       if (evaluate)
!       {
!           ga_concat(&ga, tv.vval.v_string);
!           clear_tv(&tv);
!       }
  
!       if (**arg != '{')
!       {
!           // found terminating quote
!           ++*arg;
!           break;
!       }
!       p = eval_one_expr_in_str(*arg, &ga);
!       if (p == NULL)
!       {
!           ret = FAIL;
!           break;
!       }
!       *arg = p;
!     }
  
!     rettv->v_type = VAR_STRING;
!     if (ret == FAIL || !evaluate || ga_append(&ga, NUL) == FAIL)
!     {
!       ga_clear(&ga);
!       rettv->vval.v_string = NULL;
!       return ret;
!     }
  
!     rettv->vval.v_string = ga.ga_data;
!     return OK;
  }
  
  /*
*** ../vim-8.2.4929/src/proto/typval.pro        2022-05-06 13:14:43.793076613 
+0100
--- src/proto/typval.pro        2022-05-09 21:54:39.619665829 +0100
***************
*** 68,78 ****
  int tv_equal(typval_T *tv1, typval_T *tv2, int ic, int recursive);
  int eval_option(char_u **arg, typval_T *rettv, int evaluate);
  int eval_number(char_u **arg, typval_T *rettv, int evaluate, int want_string);
! int eval_string(char_u **arg, typval_T *rettv, int evaluate);
! int eval_lit_string(char_u **arg, typval_T *rettv, int evaluate);
  char_u *tv2string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID);
  int eval_env_var(char_u **arg, typval_T *rettv, int evaluate);
- int eval_interp_string(char_u **arg, typval_T *rettv, int evaluate);
  linenr_T tv_get_lnum(typval_T *argvars);
  linenr_T tv_get_lnum_buf(typval_T *argvars, buf_T *buf);
  buf_T *tv_get_buf(typval_T *tv, int curtab_only);
--- 68,78 ----
  int tv_equal(typval_T *tv1, typval_T *tv2, int ic, int recursive);
  int eval_option(char_u **arg, typval_T *rettv, int evaluate);
  int eval_number(char_u **arg, typval_T *rettv, int evaluate, int want_string);
! int eval_string(char_u **arg, typval_T *rettv, int evaluate, int interpolate);
! int eval_lit_string(char_u **arg, typval_T *rettv, int evaluate, int 
interpolate);
! int eval_interp_string(char_u **arg, typval_T *rettv, int evaluate);
  char_u *tv2string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID);
  int eval_env_var(char_u **arg, typval_T *rettv, int evaluate);
  linenr_T tv_get_lnum(typval_T *argvars);
  linenr_T tv_get_lnum_buf(typval_T *argvars, buf_T *buf);
  buf_T *tv_get_buf(typval_T *tv, int curtab_only);
*** ../vim-8.2.4929/src/dict.c  2022-04-04 15:16:50.738014123 +0100
--- src/dict.c  2022-05-09 21:50:55.507907210 +0100
***************
*** 866,878 ****
  
      if (**arg == '\'')
      {
!       if (eval_lit_string(arg, &rettv, TRUE) == FAIL)
            return NULL;
        key = rettv.vval.v_string;
      }
      else if (**arg == '"')
      {
!       if (eval_string(arg, &rettv, TRUE) == FAIL)
            return NULL;
        key = rettv.vval.v_string;
      }
--- 866,878 ----
  
      if (**arg == '\'')
      {
!       if (eval_lit_string(arg, &rettv, TRUE, FALSE) == FAIL)
            return NULL;
        key = rettv.vval.v_string;
      }
      else if (**arg == '"')
      {
!       if (eval_string(arg, &rettv, TRUE, FALSE) == FAIL)
            return NULL;
        key = rettv.vval.v_string;
      }
*** ../vim-8.2.4929/src/eval.c  2022-05-07 21:14:01.642973330 +0100
--- src/eval.c  2022-05-09 21:51:03.947897209 +0100
***************
*** 3726,3738 ****
      /*
       * String constant: "string".
       */
!     case '"': ret = eval_string(arg, rettv, evaluate);
                break;
  
      /*
       * Literal string constant: 'str''ing'.
       */
!     case '\'':        ret = eval_lit_string(arg, rettv, evaluate);
                break;
  
      /*
--- 3726,3738 ----
      /*
       * String constant: "string".
       */
!     case '"': ret = eval_string(arg, rettv, evaluate, FALSE);
                break;
  
      /*
       * Literal string constant: 'str''ing'.
       */
!     case '\'':        ret = eval_lit_string(arg, rettv, evaluate, FALSE);
                break;
  
      /*
*** ../vim-8.2.4929/src/evalvars.c      2022-05-06 13:14:43.789076617 +0100
--- src/evalvars.c      2022-05-10 13:06:18.053795337 +0100
***************
*** 603,618 ****
  }
  
  /*
!  * Evaluate all the Vim expressions ({expr}) in string "str" and return the
!  * resulting string.  The caller must free the returned string.
   */
      char_u *
  eval_all_expr_in_str(char_u *str)
  {
      garray_T  ga;
      char_u    *p;
-     char_u    save_c;
-     char_u    *expr_val;
  
      ga_init2(&ga, 1, 80);
      p = str;
--- 603,654 ----
  }
  
  /*
!  * Evaluate one Vim expression {expr} in string "p" and append the
!  * resulting string to "gap".  "p" points to the opening "{".
!  * Return a pointer to the character after "}", NULL for an error.
!  */
!     char_u *
! eval_one_expr_in_str(char_u *p, garray_T *gap)
! {
!     char_u    *block_start = skipwhite(p + 1);  // skip the opening {
!     char_u    *block_end = block_start;
!     char_u    *expr_val;
! 
!     if (*block_start == NUL)
!     {
!       semsg(_(e_missing_close_curly_str), p);
!       return NULL;
!     }
!     if (skip_expr(&block_end, NULL) == FAIL)
!       return NULL;
!     block_end = skipwhite(block_end);
!     if (*block_end != '}')
!     {
!       semsg(_(e_missing_close_curly_str), p);
!       return NULL;
!     }
!     *block_end = NUL;
!     expr_val = eval_to_string(block_start, TRUE);
!     *block_end = '}';
!     if (expr_val == NULL)
!       return NULL;
!     ga_concat(gap, expr_val);
!     vim_free(expr_val);
! 
!     return block_end + 1;
! }
! 
! /*
!  * Evaluate all the Vim expressions {expr} in "str" and return the resulting
!  * string in allocated memory.  "{{" is reduced to "{" and "}}" to "}".
!  * Used for a heredoc assignment.
!  * Returns NULL for an error.
   */
      char_u *
  eval_all_expr_in_str(char_u *str)
  {
      garray_T  ga;
      char_u    *p;
  
      ga_init2(&ga, 1, 80);
      p = str;
***************
*** 620,627 ****
      while (*p != NUL)
      {
        char_u  *lit_start;
-       char_u  *block_start;
-       char_u  *block_end;
        int     escaped_brace = FALSE;
  
        // Look for a block start.
--- 656,661 ----
***************
*** 656,690 ****
            continue;
        }
  
!       // Skip the opening {.
!       block_start = ++p;
!       block_end = block_start;
!       if (*block_start != NUL && skip_expr(&block_end, NULL) == FAIL)
!       {
!           ga_clear(&ga);
!           return NULL;
!       }
!       block_end = skipwhite(block_end);
!       // The block must be closed by a }.
!       if (*block_end != '}')
        {
-           semsg(_(e_missing_close_curly_str), str);
            ga_clear(&ga);
            return NULL;
        }
-       save_c = *block_end;
-       *block_end = NUL;
-       expr_val = eval_to_string(block_start, TRUE);
-       *block_end = save_c;
-       if (expr_val == NULL)
-       {
-           ga_clear(&ga);
-           return NULL;
-       }
-       ga_concat(&ga, expr_val);
-       vim_free(expr_val);
- 
-       p = block_end + 1;
      }
      ga_append(&ga, NUL);
  
--- 690,702 ----
            continue;
        }
  
!       // Evaluate the expression and append the result.
!       p = eval_one_expr_in_str(p, &ga);
!       if (p == NULL)
        {
            ga_clear(&ga);
            return NULL;
        }
      }
      ga_append(&ga, NUL);
  
*** ../vim-8.2.4929/src/proto/evalvars.pro      2022-05-06 13:14:43.793076613 
+0100
--- src/proto/evalvars.pro      2022-05-09 21:55:08.719637657 +0100
***************
*** 13,18 ****
--- 13,20 ----
  int get_spellword(list_T *list, char_u **pp);
  void prepare_vimvar(int idx, typval_T *save_tv);
  void restore_vimvar(int idx, typval_T *save_tv);
+ char_u *eval_one_expr_in_str(char_u *p, garray_T *gap);
+ char_u *eval_all_expr_in_str(char_u *str);
  list_T *heredoc_get(exarg_T *eap, char_u *cmd, int script_get, int 
vim9compile);
  void ex_var(exarg_T *eap);
  void ex_let(exarg_T *eap);
***************
*** 105,110 ****
  void copy_callback(callback_T *dest, callback_T *src);
  void expand_autload_callback(callback_T *cb);
  void free_callback(callback_T *callback);
- char_u *eval_all_expr_in_str(char_u *str);
- 
  /* vim: set ft=c : */
--- 107,110 ----
*** ../vim-8.2.4929/src/vim9compile.c   2022-05-09 20:09:19.294641425 +0100
--- src/vim9compile.c   2022-05-10 12:32:36.730408912 +0100
***************
*** 969,974 ****
--- 969,1004 ----
  }
  
  /*
+  * Compile one Vim expression {expr} in string "p".
+  * "p" points to the opening "{".
+  * Return a pointer to the character after "}", NULL for an error.
+  */
+     char_u *
+ compile_one_expr_in_str(char_u *p, cctx_T *cctx)
+ {
+     char_u    *block_start;
+     char_u    *block_end;
+ 
+     // Skip the opening {.
+     block_start = skipwhite(p + 1);
+     block_end = block_start;
+     if (*block_start != NUL && skip_expr(&block_end, NULL) == FAIL)
+       return NULL;
+     block_end = skipwhite(block_end);
+     // The block must be closed by a }.
+     if (*block_end != '}')
+     {
+       semsg(_(e_missing_close_curly_str), p);
+       return NULL;
+     }
+     if (compile_expr0(&block_start, cctx) == FAIL)
+       return NULL;
+     may_generate_2STRING(-1, TRUE, cctx);
+ 
+     return block_end + 1;
+ }
+ 
+ /*
   * Compile a string "str" (either containing a literal string or a mix of
   * literal strings and Vim expressions of the form `{expr}`).  This is used
   * when compiling a heredoc assignment to a variable or an interpolated string
***************
*** 997,1004 ****
      while (*p != NUL)
      {
        char_u  *lit_start;
-       char_u  *block_start;
-       char_u  *block_end;
        int     escaped_brace = FALSE;
  
        // Look for a block start.
--- 1027,1032 ----
***************
*** 1038,1065 ****
            continue;
        }
  
!       // Skip the opening {.
!       block_start = skipwhite(p + 1);
!       block_end = block_start;
!       if (*block_start != NUL && skip_expr(&block_end, NULL) == FAIL)
!           return FAIL;
!       block_end = skipwhite(block_end);
!       // The block must be closed by a }.
!       if (*block_end != '}')
!       {
!           semsg(_(e_missing_close_curly_str), str);
            return FAIL;
-       }
-       if (compile_expr0(&block_start, cctx) == FAIL)
-           return FAIL;
-       may_generate_2STRING(-1, TRUE, cctx);
        ++count;
- 
-       p = block_end + 1;
      }
  
      // Small optimization, if there's only a single piece skip the ISN_CONCAT.
!     if (count != 1)
        return generate_CONCAT(cctx, count);
  
      return OK;
--- 1066,1079 ----
            continue;
        }
  
!       p = compile_one_expr_in_str(p, cctx);
!       if (p == NULL)
            return FAIL;
        ++count;
      }
  
      // Small optimization, if there's only a single piece skip the ISN_CONCAT.
!     if (count > 1)
        return generate_CONCAT(cctx, count);
  
      return OK;
*** ../vim-8.2.4929/src/proto/vim9compile.pro   2022-05-06 13:14:43.793076613 
+0100
--- src/proto/vim9compile.pro   2022-05-10 12:32:07.306419821 +0100
***************
*** 16,21 ****
--- 16,22 ----
  int may_get_next_line_error(char_u *whitep, char_u **arg, cctx_T *cctx);
  void fill_exarg_from_cctx(exarg_T *eap, cctx_T *cctx);
  int func_needs_compiling(ufunc_T *ufunc, compiletype_T compile_type);
+ char_u *compile_one_expr_in_str(char_u *p, cctx_T *cctx);
  int compile_all_expr_in_str(char_u *str, int evalstr, cctx_T *cctx);
  int assignment_len(char_u *p, int *heredoc);
  void vim9_declare_error(char_u *name);
*** ../vim-8.2.4929/src/vim9expr.c      2022-05-06 13:14:43.793076613 +0100
--- src/vim9expr.c      2022-05-10 13:21:14.224792916 +0100
***************
*** 762,770 ****
  
        argvars[0].v_type = VAR_UNKNOWN;
        if (*s == '"')
!           (void)eval_string(&s, &argvars[0], TRUE);
        else if (*s == '\'')
!           (void)eval_lit_string(&s, &argvars[0], TRUE);
        s = skipwhite(s);
        if (*s == ')' && argvars[0].v_type == VAR_STRING
               && ((is_has && !dynamic_feature(argvars[0].vval.v_string))
--- 762,770 ----
  
        argvars[0].v_type = VAR_UNKNOWN;
        if (*s == '"')
!           (void)eval_string(&s, &argvars[0], TRUE, FALSE);
        else if (*s == '\'')
!           (void)eval_lit_string(&s, &argvars[0], TRUE, FALSE);
        s = skipwhite(s);
        if (*s == ')' && argvars[0].v_type == VAR_STRING
               && ((is_has && !dynamic_feature(argvars[0].vval.v_string))
***************
*** 1375,1404 ****
  }
  
  /*
!  * Compile "$"string"" or "$'string'".
   */
      static int
  compile_interp_string(char_u **arg, cctx_T *cctx)
  {
      typval_T  tv;
      int               ret;
      int               evaluate = cctx->ctx_skip != SKIP_YES;
  
!     // *arg is on the '$' character.
!     (*arg)++;
  
!     if (**arg == '"')
!       ret = eval_string(arg, &tv, evaluate);
!     else
!       ret = eval_lit_string(arg, &tv, evaluate);
  
      if (ret == FAIL || !evaluate)
        return ret;
  
!     ret = compile_all_expr_in_str(tv.vval.v_string, TRUE, cctx);
!     clear_tv(&tv);
  
!     return ret;
  }
  
  /*
--- 1375,1447 ----
  }
  
  /*
!  * Compile $"string" or $'string'.
   */
      static int
  compile_interp_string(char_u **arg, cctx_T *cctx)
  {
      typval_T  tv;
      int               ret;
+     int               quote;
      int               evaluate = cctx->ctx_skip != SKIP_YES;
+     int               count = 0;
+     char_u    *p;
  
!     // *arg is on the '$' character, move it to the first string character.
!     ++*arg;
!     quote = **arg;
!     ++*arg;
  
!     for (;;)
!     {
!       // Get the string up to the matching quote or to a single '{'.
!       // "arg" is advanced to either the quote or the '{'.
!       if (quote == '"')
!           ret = eval_string(arg, &tv, evaluate, TRUE);
!       else
!           ret = eval_lit_string(arg, &tv, evaluate, TRUE);
!       if (ret == FAIL)
!           break;
!       if (evaluate)
!       {
!           if ((tv.vval.v_string != NULL && *tv.vval.v_string != NUL)
!                   || (**arg != '{' && count == 0))
!           {
!               // generate non-empty string or empty string if it's the only
!               // one
!               if (generate_PUSHS(cctx, &tv.vval.v_string) == FAIL)
!                   return FAIL;
!               tv.vval.v_string = NULL;  // don't free it now
!               ++count;
!           }
!           clear_tv(&tv);
!       }
! 
!       if (**arg != '{')
!       {
!           // found terminating quote
!           ++*arg;
!           break;
!       }
! 
!       p = compile_one_expr_in_str(*arg, cctx);
!       if (p == NULL)
!       {
!           ret = FAIL;
!           break;
!       }
!       ++count;
!       *arg = p;
!     }
  
      if (ret == FAIL || !evaluate)
        return ret;
  
!     // Small optimization, if there's only a single piece skip the ISN_CONCAT.
!     if (count > 1)
!       return generate_CONCAT(cctx, count);
  
!     return OK;
  }
  
  /*
***************
*** 2161,2174 ****
        /*
         * String constant: "string".
         */
!       case '"':   if (eval_string(arg, rettv, TRUE) == FAIL)
                        return FAIL;
                    break;
  
        /*
         * Literal string constant: 'str''ing'.
         */
!       case '\'':  if (eval_lit_string(arg, rettv, TRUE) == FAIL)
                        return FAIL;
                    break;
  
--- 2204,2217 ----
        /*
         * String constant: "string".
         */
!       case '"':   if (eval_string(arg, rettv, TRUE, FALSE) == FAIL)
                        return FAIL;
                    break;
  
        /*
         * Literal string constant: 'str''ing'.
         */
!       case '\'':  if (eval_lit_string(arg, rettv, TRUE, FALSE) == FAIL)
                        return FAIL;
                    break;
  
*** ../vim-8.2.4929/src/vim9instr.c     2022-05-04 16:46:51.349318219 +0100
--- src/vim9instr.c     2022-05-10 13:20:25.392819519 +0100
***************
*** 726,731 ****
--- 726,733 ----
  /*
   * Generate an ISN_PUSHS instruction.
   * Consumes "*str".  When freed *str is set to NULL, unless "str" is NULL.
+  * Note that if "str" is used in the instruction OK is returned and "*str" is
+  * not set to NULL.
   */
      int
  generate_PUSHS(cctx_T *cctx, char_u **str)
*** ../vim-8.2.4929/src/alloc.c 2022-04-09 11:09:03.526052266 +0100
--- src/alloc.c 2022-05-10 12:08:35.776303190 +0100
***************
*** 832,838 ****
  
  /*
   * Concatenate a string to a growarray which contains bytes.
!  * When "s" is NULL does not do anything.
   * Note: Does NOT copy the NUL at the end!
   */
      void
--- 832,838 ----
  
  /*
   * Concatenate a string to a growarray which contains bytes.
!  * When "s" is NULL memory allocation fails does not do anything.
   * Note: Does NOT copy the NUL at the end!
   */
      void
***************
*** 869,882 ****
  /*
   * Append one byte to a growarray which contains bytes.
   */
!     void
  ga_append(garray_T *gap, int c)
  {
!     if (ga_grow(gap, 1) == OK)
!     {
!       *((char *)gap->ga_data + gap->ga_len) = c;
!       ++gap->ga_len;
!     }
  }
  
  #if (defined(UNIX) && !defined(USE_SYSTEM)) || defined(MSWIN) \
--- 869,882 ----
  /*
   * Append one byte to a growarray which contains bytes.
   */
!     int
  ga_append(garray_T *gap, int c)
  {
!     if (ga_grow(gap, 1) == FAIL)
!       return FAIL;
!     *((char *)gap->ga_data + gap->ga_len) = c;
!     ++gap->ga_len;
!     return OK;
  }
  
  #if (defined(UNIX) && !defined(USE_SYSTEM)) || defined(MSWIN) \
*** ../vim-8.2.4929/src/proto/alloc.pro 2022-04-09 11:09:03.526052266 +0100
--- src/proto/alloc.pro 2022-05-10 12:08:39.188316249 +0100
***************
*** 19,31 ****
  void ga_init(garray_T *gap);
  void ga_init2(garray_T *gap, size_t itemsize, int growsize);
  int ga_grow(garray_T *gap, int n);
! int ga_grow_id(garray_T *gap, int n, alloc_id_T id UNUSED);
  int ga_grow_inner(garray_T *gap, int n);
  char_u *ga_concat_strings(garray_T *gap, char *sep);
  int ga_copy_string(garray_T *gap, char_u *p);
  int ga_add_string(garray_T *gap, char_u *p);
  void ga_concat(garray_T *gap, char_u *s);
  void ga_concat_len(garray_T *gap, char_u *s, size_t len);
! void ga_append(garray_T *gap, int c);
  void append_ga_line(garray_T *gap);
  /* vim: set ft=c : */
--- 19,31 ----
  void ga_init(garray_T *gap);
  void ga_init2(garray_T *gap, size_t itemsize, int growsize);
  int ga_grow(garray_T *gap, int n);
! int ga_grow_id(garray_T *gap, int n, alloc_id_T id);
  int ga_grow_inner(garray_T *gap, int n);
  char_u *ga_concat_strings(garray_T *gap, char *sep);
  int ga_copy_string(garray_T *gap, char_u *p);
  int ga_add_string(garray_T *gap, char_u *p);
  void ga_concat(garray_T *gap, char_u *s);
  void ga_concat_len(garray_T *gap, char_u *s, size_t len);
! int ga_append(garray_T *gap, int c);
  void append_ga_line(garray_T *gap);
  /* vim: set ft=c : */
*** ../vim-8.2.4929/src/testdir/test_expr.vim   2022-05-06 13:14:43.793076613 
+0100
--- src/testdir/test_expr.vim   2022-05-10 12:11:55.768948906 +0100
***************
*** 897,903 ****
      #" Escaping rules.
      call assert_equal('"foo"{bar}', $"\"foo\"{{bar}}")
      call assert_equal('"foo"{bar}', $'"foo"{{bar}}')
!     call assert_equal('foobar', $"{\"foo\"}" .. $'{''bar''}')
      #" Whitespace before/after the expression.
      call assert_equal('3', $"{ 1 + 2 }")
      #" String conversion.
--- 897,903 ----
      #" Escaping rules.
      call assert_equal('"foo"{bar}', $"\"foo\"{{bar}}")
      call assert_equal('"foo"{bar}', $'"foo"{{bar}}')
!     call assert_equal('foobar', $"{"foo"}" .. $'{'bar'}')
      #" Whitespace before/after the expression.
      call assert_equal('3', $"{ 1 + 2 }")
      #" String conversion.
***************
*** 907,914 ****
      call assert_equal(string(v:true), $"{v:true}")
      call assert_equal('(1+1=2)', $"(1+1={1 + 1})")
      #" Hex-escaped opening brace: char2nr('{') == 0x7b
!     call assert_equal('esc123ape', $"esc\x7b123}ape")
!     call assert_equal('me{}me', $"me{\x7b}\x7dme")
      VAR var1 = "sun"
      VAR var2 = "shine"
      call assert_equal('sunshine', $"{var1}{var2}")
--- 907,914 ----
      call assert_equal(string(v:true), $"{v:true}")
      call assert_equal('(1+1=2)', $"(1+1={1 + 1})")
      #" Hex-escaped opening brace: char2nr('{') == 0x7b
!     call assert_equal('esc123ape', $"esc{123}ape")
!     call assert_equal('me{}me', $"me{"\x7b"}\x7dme")
      VAR var1 = "sun"
      VAR var2 = "shine"
      call assert_equal('sunshine', $"{var1}{var2}")
***************
*** 916,922 ****
      #" Multibyte strings.
      call assert_equal('say ハロー・ワールド', $"say {'ハロー・ワールド'}")
      #" Nested.
!     call assert_equal('foobarbaz', $"foo{$\"{'bar'}\"}baz")
      #" Do not evaluate blocks when the expr is skipped.
      VAR tmp = 0
      if v:false
--- 916,922 ----
      #" Multibyte strings.
      call assert_equal('say ハロー・ワールド', $"say {'ハロー・ワールド'}")
      #" Nested.
!     call assert_equal('foobarbaz', $"foo{$"{'bar'}"}baz")
      #" Do not evaluate blocks when the expr is skipped.
      VAR tmp = 0
      if v:false
*** ../vim-8.2.4929/src/testdir/test_let.vim    2022-05-06 13:14:43.793076613 
+0100
--- src/testdir/test_let.vim    2022-05-10 13:12:25.437216707 +0100
***************
*** 387,395 ****
    let text = 'text'
    call assert_equal('text{{', $'{text .. "{{"}')
    call assert_equal('text{{', $"{text .. '{{'}")
!   " FIXME: should not need to escape quotes in the expression
!   call assert_equal('text{{', $'{text .. ''{{''}')
!   call assert_equal('text{{', $"{text .. \"{{\"}")
  endfunc
  
  " Test for the setting a variable using the heredoc syntax.
--- 387,394 ----
    let text = 'text'
    call assert_equal('text{{', $'{text .. "{{"}')
    call assert_equal('text{{', $"{text .. '{{'}")
!   call assert_equal('text{{', $'{text .. '{{'}')
!   call assert_equal('text{{', $"{text .. "{{"}")
  endfunc
  
  " Test for the setting a variable using the heredoc syntax.
*** ../vim-8.2.4929/src/version.c       2022-05-09 21:03:30.781853316 +0100
--- src/version.c       2022-05-10 11:11:45.423852626 +0100
***************
*** 748,749 ****
--- 748,751 ----
  {   /* Add new patch number below this line */
+ /**/
+     4930,
  /**/

-- 
Send $25.00 for handy leaflet on how to make money by selling leaflets

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20220510122524.D35021C0645%40moolenaar.net.

Raspunde prin e-mail lui