patch 8.2.4883: string interpolation only works in heredoc

Problem:    String interpolation only works in heredoc.
Solution:   Support interpolated strings.  Use syntax for heredoc consistent
            with strings, similar to C#. (closes #10327)
diff --git a/src/testdir/test_debugger.vim b/src/testdir/test_debugger.vim
index 5aa7bab..17d3616 100644
--- a/src/testdir/test_debugger.vim
+++ b/src/testdir/test_debugger.vim
@@ -377,7 +377,7 @@
   let expected =<< eval trim END
     Oldval = "10"
     Newval = "11"
-    `=fnamemodify('Xtest.vim', ':p')`
+    {fnamemodify('Xtest.vim', ':p')}
     line 1: let g:Xtest_var += 1
   END
   call RunDbgCmd(buf, ':source %', expected)
@@ -385,7 +385,7 @@
   let expected =<< eval trim END
     Oldval = "11"
     Newval = "12"
-    `=fnamemodify('Xtest.vim', ':p')`
+    {fnamemodify('Xtest.vim', ':p')}
     line 1: let g:Xtest_var += 1
   END
   call RunDbgCmd(buf, ':source %', expected)
diff --git a/src/testdir/test_expr.vim b/src/testdir/test_expr.vim
index 3c61d27..3ea59eb 100644
--- a/src/testdir/test_expr.vim
+++ b/src/testdir/test_expr.vim
@@ -890,4 +890,60 @@
   call v9.CheckLegacyAndVim9Success(lines)
 endfunc
 
+func Test_string_interp()
+  let lines =<< trim END
+    call assert_equal('', $"")
+    call assert_equal('foobar', $"foobar")
+    #" 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.
+    call assert_equal('hello from ' .. v:version, $"hello from {v:version}")
+    call assert_equal('hello from ' .. v:version, $'hello from {v:version}')
+    #" Paper over a small difference between VimScript behaviour.
+    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}")
+    call assert_equal('sunsunsun', $"{var1->repeat(3)}")
+    #" 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
+      echo "${ LET tmp += 1 }"
+    endif
+    call assert_equal(0, tmp)
+
+    #" Stray closing brace.
+    call assert_fails('echo $"moo}"', 'E1278:')
+    #" Undefined variable in expansion.
+    call assert_fails('echo $"{moo}"', 'E121:')
+    #" Empty blocks are rejected.
+    call assert_fails('echo $"{}"', 'E15:')
+    call assert_fails('echo $"{   }"', 'E15:')
+  END
+  call v9.CheckLegacyAndVim9Success(lines)
+
+  let lines =<< trim END
+    call assert_equal('5', $"{({x -> x + 1})(4)}")
+  END
+  call v9.CheckLegacySuccess(lines)
+
+  let lines =<< trim END
+    call assert_equal('5', $"{((x) => x + 1)(4)}")
+    call assert_fails('echo $"{ # foo }"', 'E1279:')
+  END
+  call v9.CheckDefAndScriptSuccess(lines)
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/testdir/test_let.vim b/src/testdir/test_let.vim
index 32f91f6..7e1a923 100644
--- a/src/testdir/test_let.vim
+++ b/src/testdir/test_let.vim
@@ -381,6 +381,17 @@
   call assert_equal(['Text', 'with', 'indent'], text)
 endfunc
 
+func Test_let_interpolated()
+  call assert_equal('{text}', $'{{text}}')
+  call assert_equal('{{text}}', $'{{{{text}}}}')
+  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.
 " Keep near the end, this messes up highlighting.
 func Test_let_heredoc()
@@ -498,72 +509,72 @@
   call assert_equal(['     x', '     \y', '     z'], [a, b, c])
 endfunc
 
-" Test for evaluating Vim expressions in a heredoc using `=expr`
+" Test for evaluating Vim expressions in a heredoc using {expr}
 " Keep near the end, this messes up highlighting.
 func Test_let_heredoc_eval()
   let str = ''
   let code =<< trim eval END
-    let a = `=5 + 10`
-    let b = `=min([10, 6])` + `=max([4, 6])`
-    `=str`
-    let c = "abc`=str`d"
+    let a = {5 + 10}
+    let b = {min([10, 6])} + {max([4, 6])}
+    {str}
+    let c = "abc{str}d"
   END
   call assert_equal(['let a = 15', 'let b = 6 + 6', '', 'let c = "abcd"'], code)
 
   let $TESTVAR = "Hello"
   let code =<< eval trim END
-    let s = "`=$TESTVAR`"
+    let s = "{$TESTVAR}"
   END
   call assert_equal(['let s = "Hello"'], code)
 
   let code =<< eval END
-    let s = "`=$TESTVAR`"
+    let s = "{$TESTVAR}"
 END
   call assert_equal(['    let s = "Hello"'], code)
 
   let a = 10
   let data =<< eval END
-`=a`
+{a}
 END
   call assert_equal(['10'], data)
 
   let x = 'X'
   let code =<< eval trim END
-    let a = `abc`
-    let b = `=x`
-    let c = `
+    let a = {{abc}}
+    let b = {x}
+    let c = {{
   END
-  call assert_equal(['let a = `abc`', 'let b = X', 'let c = `'], code)
+  call assert_equal(['let a = {abc}', 'let b = X', 'let c = {'], code)
 
   let code = 'xxx'
   let code =<< eval trim END
-    let n = `=5 +
-    6`
+    let n = {5 +
+    6}
   END
   call assert_equal('xxx', code)
 
   let code =<< eval trim END
-     let n = `=min([1, 2]` + `=max([3, 4])`
+     let n = {min([1, 2]} + {max([3, 4])}
   END
   call assert_equal('xxx', code)
 
   let lines =<< trim LINES
       let text =<< eval trim END
-        let b = `=
+        let b = {
       END
   LINES
-  call v9.CheckScriptFailure(lines, 'E1083:')
+  call v9.CheckScriptFailure(lines, 'E1279:')
 
   let lines =<< trim LINES
       let text =<< eval trim END
-        let b = `=abc
+        let b = {abc
       END
   LINES
-  call v9.CheckScriptFailure(lines, 'E1083:')
+  call v9.CheckScriptFailure(lines, 'E1279:')
 
   let lines =<< trim LINES
       let text =<< eval trim END
-        let b = `=`
+        let b = {}
       END
   LINES
   call v9.CheckScriptFailure(lines, 'E15:')
@@ -571,7 +582,7 @@
   " skipped heredoc
   if 0
     let msg =<< trim eval END
-        n is: `=n`
+        n is: {n}
     END
   endif
 
@@ -583,7 +594,7 @@
   let lines =<< trim END
     let Xvar =<< eval CODE
     let a = 1
-    let b = `=5+`
+    let b = {5+}
     let c = 2
     CODE
     let g:Count += 1
@@ -592,10 +603,10 @@
   let g:Count = 0
   call assert_fails('source', 'E15:')
   call assert_equal(1, g:Count)
-  call setline(3, 'let b = `=abc`')
+  call setline(3, 'let b = {abc}')
   call assert_fails('source', 'E121:')
   call assert_equal(2, g:Count)
-  call setline(3, 'let b = `=abc` + `=min([9, 4])` + 2')
+  call setline(3, 'let b = {abc} + {min([9, 4])} + 2')
   call assert_fails('source', 'E121:')
   call assert_equal(3, g:Count)
   call assert_equal('test', g:Xvar)
diff --git a/src/testdir/test_vim9_assign.vim b/src/testdir/test_vim9_assign.vim
index 2c3db65..97a6970 100644
--- a/src/testdir/test_vim9_assign.vim
+++ b/src/testdir/test_vim9_assign.vim
@@ -2670,10 +2670,10 @@
     var a3 = "3"
     var a4 = ""
     var code =<< trim eval END
-      var a = `=5 + 10`
-      var b = `=min([10, 6])` + `=max([4, 6])`
-      var c = "`=s`"
-      var d = x`=a1`x`=a2`x`=a3`x`=a4`
+      var a = {5 + 10}
+      var b = {min([10, 6])} + {max([4, 6])}
+      var c = "{s}"
+      var d = x{a1}x{a2}x{a3}x{a4}
     END
     assert_equal(['var a = 15', 'var b = 6 + 6', 'var c = "local"', 'var d = x1x2x3x'], code)
   CODE
@@ -2681,7 +2681,7 @@
 
   lines =<< trim CODE
     var code =<< eval trim END
-      var s = "`=$SOME_ENV_VAR`"
+      var s = "{$SOME_ENV_VAR}"
     END
     assert_equal(['var s = "somemore"'], code)
   CODE
@@ -2689,7 +2689,7 @@
 
   lines =<< trim CODE
     var code =<< eval END
-      var s = "`=$SOME_ENV_VAR`"
+      var s = "{$SOME_ENV_VAR}"
     END
     assert_equal(['  var s = "somemore"'], code)
   CODE
@@ -2697,34 +2697,34 @@
 
   lines =<< trim CODE
     var code =<< eval trim END
-      let a = `abc`
-      let b = `=g:someVar`
-      let c = `
+      let a = {{abc}}
+      let b = {g:someVar}
+      let c = {{
     END
-    assert_equal(['let a = `abc`', 'let b = X', 'let c = `'], code)
+    assert_equal(['let a = {abc}', 'let b = X', 'let c = {'], code)
   CODE
   v9.CheckDefAndScriptSuccess(lines)
 
   lines =<< trim LINES
       var text =<< eval trim END
-        let b = `=
+        let b = {
       END
   LINES
-  v9.CheckDefAndScriptFailure(lines, ['E1143: Empty expression: ""', 'E1083: Missing backtick'])
+  v9.CheckDefAndScriptFailure(lines, "E1279: Missing '}'")
 
   lines =<< trim LINES
       var text =<< eval trim END
-        let b = `=abc
+        let b = {abc
       END
   LINES
-  v9.CheckDefAndScriptFailure(lines, ['E1001: Variable not found: abc', 'E1083: Missing backtick'])
+  v9.CheckDefAndScriptFailure(lines, "E1279: Missing '}'")
 
   lines =<< trim LINES
       var text =<< eval trim END
-        let b = `=`
+        let b = {}
       END
   LINES
-  v9.CheckDefAndScriptFailure(lines, ['E1015: Name expected: `', 'E15: Invalid expression: "`"'])
+  v9.CheckDefAndScriptFailure(lines, 'E15: Invalid expression: "}"')
 enddef
 
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index e42ee78..3ab3c0d 100644
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -2840,6 +2840,25 @@
     delfunc g:ThatFunc
 enddef
 
+def s:MakeString(x: number): string
+  return $"x={x} x^2={x * x}"
+enddef
 
+def Test_disassemble_string_interp()
+  var instr = execute('disassemble s:MakeString')
+  assert_match('MakeString\_s*' ..
+        'return $"x={x} x^2={x \* x}"\_s*' ..
+        '0 PUSHS "x="\_s*' ..
+        '1 LOAD arg\[-1\]\_s*' ..
+        '2 2STRING stack\[-1\]\_s*' ..
+        '3 PUSHS " x^2="\_s*' ..
+        '4 LOAD arg\[-1\]\_s*' ..
+        '5 LOAD arg\[-1\]\_s*' ..
+        '6 OPNR \*\_s*' ..
+        '7 2STRING stack\[-1\]\_s*' ..
+        '8 CONCAT size 4\_s*' ..
+        '9 RETURN\_s*',
+        instr)
+enddef
 
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker