changeset 22936:00b0275ffe7f v8.2.2015

patch 8.2.2015: Vim9: literal dict #{} is not like any other language Commit: https://github.com/vim/vim/commit/2bede173a177e227e6224a8713f5b88a38d011af Author: Bram Moolenaar <Bram@vim.org> Date: Thu Nov 19 18:53:18 2020 +0100 patch 8.2.2015: Vim9: literal dict #{} is not like any other language Problem: Vim9: literal dict #{} is not like any other language. Solution: Support the JavaScript syntax.
author Bram Moolenaar <Bram@vim.org>
date Thu, 19 Nov 2020 19:00:06 +0100
parents da7ce666cf1f
children 77a956145f6a
files runtime/doc/vim9.txt src/errors.h src/proto/vim9compile.pro src/testdir/test_vim9_builtin.vim src/testdir/test_vim9_expr.vim src/testdir/test_vim9_func.vim src/testdir/test_vim9_script.vim src/version.c src/vim9compile.c
diffstat 9 files changed, 72 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/vim9.txt
+++ b/runtime/doc/vim9.txt
@@ -112,8 +112,7 @@ In Vi # is a command to list text with n
 	101 number
 
 To improve readability there must be a space between a command and the #
-that starts a comment.  Note that #{ is the start of a dictionary, therefore
-it does not start a comment.
+that starts a comment.
 
 
 Vim9 functions ~
@@ -303,8 +302,7 @@ identifier or can't be an Ex command.  E
 	myList->add(123)
 	g:myList->add(123)
 	[1, 2, 3]->Process()
-	#{a: 1, b: 2}->Process()
-	{'a': 1, 'b': 2}->Process()
+	{a: 1, b: 2}->Process()
 	"foobar"->Process()
 	("foobar")->Process()
 	'foobar'->Process()
@@ -346,7 +344,7 @@ those cases there is no need to prefix t
 		'two',
 		]
 And when a dict spans multiple lines: >
-	var mydict = #{
+	var mydict = {
 		one: 1,
 		two: 2,
 		}
@@ -430,6 +428,27 @@ No curly braces expansion ~
 |curly-braces-names| cannot be used.
 
 
+Dictionary literals ~
+
+Traditionally Vim has supported dictionary literals with a {} syntax: >
+	let dict = {'key': value}
+
+Later it became clear that using a simple key name is very common, thus
+literally dictionaries were introduced in a backwards compatible way: >
+	let dict = #{key: value}
+
+However, this #{} syntax is unlike any existing language.  As it appears that
+using a literaly key is much more common than using an expression, and
+considering that JavaScript uses this syntax, using the {} form for dictionary
+literals was considered a much more useful syntax.  In Vim9 script the {} form
+uses literal keys: >
+	let dict = {key: value}
+
+In case an expression needs to be used for the key, square brackets can be
+used, just like in JavaScript: >
+	let dict = {["key" .. nr]: value}
+
+
 No :xit, :t, :append, :change or :insert ~
 
 These commands are too easily confused with local variable names.
--- a/src/errors.h
+++ b/src/errors.h
@@ -303,3 +303,5 @@ EXTERN char e_cmd_maping_must_not_includ
 	INIT(= N_("E1137: <Cmd> mapping must not include %s key"));
 EXTERN char e_using_bool_as_number[]
 	INIT(= N_("E1138: Using a Bool as a Number"));
+EXTERN char e_missing_matching_bracket_after_dict_key[]
+	INIT(= N_("E1139: Missing matching bracket after dict key"));
--- a/src/proto/vim9compile.pro
+++ b/src/proto/vim9compile.pro
@@ -8,6 +8,7 @@ imported_T *find_imported_in_script(char
 int vim9_comment_start(char_u *p);
 char_u *peek_next_line_from_context(cctx_T *cctx);
 char_u *next_line_from_context(cctx_T *cctx, int skip_comment);
+char_u *to_name_end(char_u *arg, int namespace);
 char_u *to_name_const_end(char_u *arg);
 exptype_T get_compare_type(char_u *p, int *len, int *type_is);
 void error_white_both(char_u *op, int len);
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -226,7 +226,7 @@ enddef
 
 
 def Wrong_dict_key_type(items: list<number>): list<number>
-  return filter(items, {_, val -> get({val: 1}, 'x')})
+  return filter(items, {_, val -> get({[val]: 1}, 'x')})
 enddef
 
 def Test_filter_wrong_dict_key_type()
--- a/src/testdir/test_vim9_expr.vim
+++ b/src/testdir/test_vim9_expr.vim
@@ -1883,6 +1883,9 @@ def Test_epxr7_funcref()
   CheckDefAndScriptSuccess(lines)
 enddef
 
+let g:test_space_dict = {'': 'empty', ' ': 'space'}
+let g:test_hash_dict = #{one: 1, two: 2}
+
 def Test_expr7_dict()
   # dictionary
   var lines =<< trim END
@@ -1891,17 +1894,17 @@ def Test_expr7_dict()
       assert_equal(g:dict_one, {'one': 1})
       var key = 'one'
       var val = 1
-      assert_equal(g:dict_one, {key: val})
+      assert_equal(g:dict_one, {[key]: val})
 
-      var numbers: dict<number> = #{a: 1, b: 2, c: 3}
+      var numbers: dict<number> = {a: 1, b: 2, c: 3}
       numbers = #{a: 1}
       numbers = #{}
 
-      var strings: dict<string> = #{a: 'a', b: 'b', c: 'c'}
+      var strings: dict<string> = {a: 'a', b: 'b', c: 'c'}
       strings = #{a: 'x'}
       strings = #{}
 
-      var mixed: dict<any> = #{a: 'a', b: 42}
+      var mixed: dict<any> = {a: 'a', b: 42}
       mixed = #{a: 'x'}
       mixed = #{a: 234}
       mixed = #{}
@@ -1915,6 +1918,9 @@ def Test_expr7_dict()
       dictdict = #{one: #{}, two: #{}}
 
       assert_equal({'': 0}, {matchstr('string', 'wont match'): 0})
+
+      assert_equal(g:test_space_dict, {['']: 'empty', [' ']: 'space'})
+      assert_equal(g:test_hash_dict, {one: 1, two: 2})
   END
   CheckDefAndScriptSuccess(lines)
  
@@ -1929,7 +1935,7 @@ def Test_expr7_dict()
   CheckDefFailure(["var x = #{xxx: 1", "var y = 2"], 'E722:', 2)
   CheckDefFailure(["var x = #{xxx: 1,"], 'E723:', 2)
   CheckDefFailure(["var x = {'a': xxx}"], 'E1001:', 1)
-  CheckDefFailure(["var x = {xxx: 8}"], 'E1001:', 1)
+  CheckDefFailure(["var x = {xx-x: 8}"], 'E1001:', 1)
   CheckDefFailure(["var x = #{a: 1, a: 2}"], 'E721:', 1)
   CheckDefFailure(["var x = #"], 'E1015:', 1)
   CheckDefExecFailure(["var x = g:anint.member"], 'E715:', 1)
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -1569,7 +1569,7 @@ enddef
 def TreeWalk(dir: string): list<any>
   return readdir(dir)->map({_, val ->
             fnamemodify(dir .. '/' .. val, ':p')->isdirectory()
-               ? {val: TreeWalk(dir .. '/' .. val)}
+               ? {[val]: TreeWalk(dir .. '/' .. val)}
                : val
              })
 enddef
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -416,7 +416,7 @@ def Test_try_catch()
 
   var nd: dict<any>
   try
-    nd = {g:anumber: 1}
+    nd = {[g:anumber]: 1}
   catch /E1012:/
     n = 266
   endtry
@@ -459,7 +459,7 @@ def Test_try_catch()
   assert_equal(322, n)
 
   try
-    d = {'text': 1, g:astring: 2}
+    d = {text: 1, [g:astring]: 2}
   catch /E721:/
     n = 333
   endtry
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2015,
+/**/
     2014,
 /**/
     2013,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -2771,7 +2771,7 @@ theend:
  * Return a pointer to just after the name.  Equal to "arg" if there is no
  * valid name.
  */
-    static char_u *
+    char_u *
 to_name_end(char_u *arg, int namespace)
 {
     char_u	*p;
@@ -2988,7 +2988,8 @@ compile_dict(char_u **arg, cctx_T *cctx,
     *arg = skipwhite(*arg + 1);
     for (;;)
     {
-	char_u *key = NULL;
+	char_u	    *key = NULL;
+	char_u	    *end;
 
 	if (may_get_next_line(whitep, arg, cctx) == FAIL)
 	{
@@ -2999,10 +3000,14 @@ compile_dict(char_u **arg, cctx_T *cctx,
 	if (**arg == '}')
 	    break;
 
-	if (literal)
-	{
-	    char_u *end = to_name_end(*arg, !literal);
-
+	// Eventually {name: value} will use "name" as a literal key and
+	// {[expr]: value} for an evaluated key.
+	// Temporarily: if "name" is indeed a valid key, or "[expr]" is
+	// used, use the new method, like JavaScript.  Otherwise fall back
+	// to the old method.
+	end = to_name_end(*arg, FALSE);
+	if (literal || *end == ':')
+	{
 	    if (end == *arg)
 	    {
 		semsg(_(e_invalid_key_str), *arg);
@@ -3015,8 +3020,11 @@ compile_dict(char_u **arg, cctx_T *cctx,
 	}
 	else
 	{
-	    isn_T		*isn;
-
+	    isn_T	*isn;
+	    int		has_bracket = **arg == '[';
+
+	    if (has_bracket)
+		*arg = skipwhite(*arg + 1);
 	    if (compile_expr0(arg, cctx) == FAIL)
 		return FAIL;
 	    isn = ((isn_T *)instr->ga_data) + instr->ga_len - 1;
@@ -3025,11 +3033,21 @@ compile_dict(char_u **arg, cctx_T *cctx,
 	    else
 	    {
 		type_T *keytype = ((type_T **)stack->ga_data)
-							   [stack->ga_len - 1];
+						       [stack->ga_len - 1];
 		if (need_type(keytype, &t_string, -1, cctx,
-							 FALSE, FALSE) == FAIL)
+						     FALSE, FALSE) == FAIL)
 		    return FAIL;
 	    }
+	    if (has_bracket)
+	    {
+		*arg = skipwhite(*arg);
+		if (**arg != ']')
+		{
+		    emsg(_(e_missing_matching_bracket_after_dict_key));
+		    return FAIL;
+		}
+		++*arg;
+	    }
 	}
 
 	// Check for duplicate keys, if using string keys.