# HG changeset patch # User Bram Moolenaar # Date 1605808806 -3600 # Node ID 00b0275ffe7f6248591d0e2e34797c771b2912a5 # Parent da7ce666cf1f81e8ee4e6c3b1df8dc7695874ba7 patch 8.2.2015: Vim9: literal dict #{} is not like any other language Commit: https://github.com/vim/vim/commit/2bede173a177e227e6224a8713f5b88a38d011af Author: Bram Moolenaar 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. diff --git a/runtime/doc/vim9.txt b/runtime/doc/vim9.txt --- 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. diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -303,3 +303,5 @@ EXTERN char e_cmd_maping_must_not_includ INIT(= N_("E1137: 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")); diff --git a/src/proto/vim9compile.pro b/src/proto/vim9compile.pro --- 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); diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim --- 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): list - return filter(items, {_, val -> get({val: 1}, 'x')}) + return filter(items, {_, val -> get({[val]: 1}, 'x')}) enddef def Test_filter_wrong_dict_key_type() diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim --- 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 = #{a: 1, b: 2, c: 3} + var numbers: dict = {a: 1, b: 2, c: 3} numbers = #{a: 1} numbers = #{} - var strings: dict = #{a: 'a', b: 'b', c: 'c'} + var strings: dict = {a: 'a', b: 'b', c: 'c'} strings = #{a: 'x'} strings = #{} - var mixed: dict = #{a: 'a', b: 42} + var mixed: dict = {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) diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -1569,7 +1569,7 @@ enddef def TreeWalk(dir: string): list return readdir(dir)->map({_, val -> fnamemodify(dir .. '/' .. val, ':p')->isdirectory() - ? {val: TreeWalk(dir .. '/' .. val)} + ? {[val]: TreeWalk(dir .. '/' .. val)} : val }) enddef diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- 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 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 diff --git a/src/version.c b/src/version.c --- 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, diff --git a/src/vim9compile.c b/src/vim9compile.c --- 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.