Skip to content

Commit 96b271d

Browse files
authored
[osh] Implement Bash 5.1 assoc=(key value ...) (#2281)
* [core/bash_impl] Disallow `assoc=(key value ...)` with strict_array
1 parent b363822 commit 96b271d

File tree

7 files changed

+74
-55
lines changed

7 files changed

+74
-55
lines changed

builtin/assign_osh.py

+3-11
Original file line numberDiff line numberDiff line change
@@ -221,14 +221,6 @@ def _AssignVarForBuiltin(mem, rval, pair, which_scopes, flags, arith_ev,
221221
pair.blame_word)
222222
initializer = cast(value.InitializerList, rval)
223223

224-
# OSH compatibility: "declare -A assoc=(1 2)" is a usage
225-
# error. This code will soon be removed when the
226-
# initialization of the form "assoc=(key value)" is supported.
227-
if (flag_A and len(initializer.assigns) > 0 and
228-
initializer.assigns[0].key is None):
229-
e_usage("Can't initialize assoc array with indexed array",
230-
pair.blame_word)
231-
232224
val = old_val
233225
if flag_a:
234226
if old_val.tag() in (value_e.Undef, value_e.Str,
@@ -270,8 +262,8 @@ def _AssignVarForBuiltin(mem, rval, pair, which_scopes, flags, arith_ev,
270262
"Can't convert type %s into BashAssoc" %
271263
ui.ValType(old_val), pair.blame_word)
272264

273-
val = cmd_eval.ListInitializeTarget(val, initializer, pair.plus_eq,
274-
mem.exec_opts, pair.blame_word)
265+
val = cmd_eval.ListInitializeTarget(val, pair.plus_eq, mem.exec_opts,
266+
pair.blame_word)
275267
elif pair.plus_eq:
276268
old_val = sh_expr_eval.OldValue(
277269
lval,
@@ -284,7 +276,7 @@ def _AssignVarForBuiltin(mem, rval, pair, which_scopes, flags, arith_ev,
284276

285277
mem.SetNamed(lval, val, which_scopes, flags=flags)
286278
if initializer is not None:
287-
cmd_eval.ListInitialize(val, initializer, pair.plus_eq,
279+
cmd_eval.ListInitialize(val, initializer, pair.plus_eq, mem.exec_opts,
288280
pair.blame_word, arith_ev)
289281

290282

core/bash_impl.py

+25-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from _devbuild.gen.value_asdl import value
55
from _devbuild.gen.syntax_asdl import loc_t
66

7+
from core import optview
78
from core.error import e_die
89
from data_lang import j8_lite
910
from mycpp import mops
@@ -293,12 +294,34 @@ def BashAssoc_Copy(val):
293294
return value.BashAssoc(d)
294295

295296

296-
def BashAssoc_ListInitialize(val, initializer, has_plus, blame_loc):
297-
# type: (value.BashAssoc, value.InitializerList, bool, loc_t) -> None
297+
def BashAssoc_ListInitialize(val, initializer, has_plus, exec_opts, blame_loc):
298+
# type: (value.BashAssoc, value.InitializerList, bool, optview.Exec, loc_t) -> None
298299

299300
if not has_plus:
300301
val.d.clear()
301302

303+
if len(initializer.assigns) > 0 and initializer.assigns[0].key is None:
304+
if exec_opts.strict_array():
305+
e_die(
306+
"BashAssoc cannot be list-initialzied by (KEY VALUE ...) (strict_array)",
307+
blame_loc)
308+
309+
# Process the form "a=(key1 value1 key2 value2 ...)"
310+
k = None # type: Optional[str]
311+
for assign in initializer.assigns:
312+
s = assign.rval
313+
if assign.key is not None:
314+
s = '[%s]%s%s' % (assign.key, '+=' if assign.plus_eq else '=',
315+
s)
316+
if k is not None:
317+
val.d[k] = s
318+
k = None
319+
else:
320+
k = s
321+
if k is not None:
322+
val.d[k] = ''
323+
return
324+
302325
for triplet in initializer.assigns:
303326
if triplet.key is None:
304327
e_die(

doc/ref/chap-osh-assign.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,6 @@ item in order. An item in the initializer list must be in the forms
240240
declare -A a=() # This creates an empty BashAssoc
241241
declare -A a=([a]=1 [b]=2) # This creates a BashAssoc with two elements
242242

243-
declare -A a=(1 2) # This is an error (OSH)
244-
245243
k=10
246244
declare -A a=([k]=v) # This creates a BashAssoc with one element,
247245
# (['k']=1). Unlike BashArray, "k" is not
@@ -251,6 +249,13 @@ item in order. An item in the initializer list must be in the forms
251249
a+=([k]=5) # This overwrites an element in the original
252250
# array. The result is ([a]=3 [b]=4 [k]=5).
253251

252+
As a special rule, when the first initializer form does not have `[KEY]=` or
253+
`[KEY]+=`, the initializer items are treated as a sequence of `KEY1 VALUE1 KEY2
254+
VALUE2 KEY3 VALUE3 ...`. OSH disables this feature when `strict_array` is set.
255+
256+
declare -A a=(1 2 3 4) # This creates a BashAssoc with two elements,
257+
# ([1]=2 [3]=4)
258+
254259
In YSH, use a [dict-literal][] to create a [Dict][] instance.
255260

256261
[BashAssoc]: chap-type-method.html#BashAssoc

osh/cmd_eval.py

+10-19
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,11 @@ def _HasManyStatuses(node):
191191

192192

193193
def ListInitializeTarget(old_val,
194-
initializer,
195194
has_plus,
196195
exec_opts,
197196
blame_loc,
198197
destructive=True):
199-
# type: (value_t, value.InitializerList, bool, optview.Exec, loc_t, bool) -> value_t
198+
# type: (value_t, bool, optview.Exec, loc_t, bool) -> value_t
200199
UP_old_val = old_val
201200
with tagswitch(old_val) as case:
202201
if case(value_e.Undef):
@@ -219,13 +218,6 @@ def ListInitializeTarget(old_val,
219218
old_val = bash_impl.BashArray_New()
220219
return old_val
221220
elif case(value_e.BashAssoc):
222-
# OSH compatibility: assoc=(1 2) will create a new array. This
223-
# code will soon be removed when the initialization of the form
224-
# "assoc=(key value)" is supported.
225-
if (not has_plus and len(initializer.assigns) > 0 and
226-
initializer.assigns[0].key is None):
227-
return bash_impl.BashArray_New()
228-
229221
old_val = cast(value.BashAssoc, UP_old_val)
230222
if not destructive:
231223
if has_plus:
@@ -239,8 +231,8 @@ def ListInitializeTarget(old_val,
239231
ui.ValType(old_val), blame_loc)
240232

241233

242-
def ListInitialize(val, initializer, has_plus, blame_loc, arith_ev):
243-
# type: (value_t, value.InitializerList, bool, loc_t, sh_expr_eval.ArithEvaluator) -> None
234+
def ListInitialize(val, initializer, has_plus, exec_opts, blame_loc, arith_ev):
235+
# type: (value_t, value.InitializerList, bool, optview.Exec, loc_t, sh_expr_eval.ArithEvaluator) -> None
244236
UP_val = val
245237
with tagswitch(val) as case:
246238
if case(value_e.BashArray):
@@ -250,7 +242,7 @@ def ListInitialize(val, initializer, has_plus, blame_loc, arith_ev):
250242
elif case(value_e.BashAssoc):
251243
val = cast(value.BashAssoc, UP_val)
252244
bash_impl.BashAssoc_ListInitialize(val, initializer, has_plus,
253-
blame_loc)
245+
exec_opts, blame_loc)
254246
else:
255247
raise AssertionError(val.tag())
256248

@@ -627,7 +619,6 @@ def _EvalTempEnv(self, more_env, flags):
627619
None, # No nounset
628620
e_pair.left)
629621
val = ListInitializeTarget(old_val,
630-
initializer,
631622
has_plus,
632623
self.exec_opts,
633624
e_pair.left,
@@ -641,8 +632,8 @@ def _EvalTempEnv(self, more_env, flags):
641632
flags=flags)
642633

643634
if initializer is not None:
644-
ListInitialize(val, initializer, has_plus, e_pair.left,
645-
self.arith_ev)
635+
ListInitialize(val, initializer, has_plus, self.exec_opts,
636+
e_pair.left, self.arith_ev)
646637

647638
def _StrictErrExit(self, node):
648639
# type: (command_t) -> None
@@ -1045,8 +1036,8 @@ def _DoShAssignment(self, node, cmd_st):
10451036
old_val = sh_expr_eval.OldValue(lval, self.mem, None,
10461037
node.left)
10471038

1048-
val = ListInitializeTarget(old_val, initializer, has_plus,
1049-
self.exec_opts, pair.left)
1039+
val = ListInitializeTarget(old_val, has_plus, self.exec_opts,
1040+
pair.left)
10501041

10511042
elif has_plus:
10521043
# do not respect set -u
@@ -1065,8 +1056,8 @@ def _DoShAssignment(self, node, cmd_st):
10651056
flags = 0 # for tracing
10661057
self.mem.SetValue(lval, val, which_scopes, flags=flags)
10671058
if initializer is not None:
1068-
ListInitialize(val, initializer, has_plus, pair.left,
1069-
self.arith_ev)
1059+
ListInitialize(val, initializer, has_plus, self.exec_opts,
1060+
pair.left, self.arith_ev)
10701061

10711062
self.tracer.OnShAssignment(lval, pair.op, rhs, flags, which_scopes)
10721063

spec/array-assoc.test.sh

+15-21
Original file line numberDiff line numberDiff line change
@@ -30,42 +30,36 @@ c
3030

3131
#### set associative array to indexed array literal (very surprising bash behavior)
3232
declare -A assoc=([k1]=foo [k2]='spam eggs')
33-
for v in "${assoc[@]}"; do echo $v; done | sort
34-
for v in "${!assoc[@]}"; do echo $v; done | sort
33+
declare -p assoc
3534

36-
# disallow this in OSH? Changing type?
35+
# Bash 5.1 assoc=(key value). Bash 5.0 (including the currently tested 4.4)
36+
# does not implement this.
3737

3838
assoc=(foo 'spam eggs')
39-
argv.py "${assoc[@]}"
40-
argv.py "${!assoc[@]}"
39+
declare -p assoc
4140

4241
## STDOUT:
43-
foo
44-
spam eggs
45-
k1
46-
k2
47-
['foo', 'spam eggs']
48-
['0', '1']
42+
declare -A assoc=(['k1']=foo ['k2']='spam eggs')
43+
declare -A assoc=(['foo']='spam eggs')
4944
## END
50-
## BUG bash STDOUT:
51-
foo
52-
spam eggs
53-
k1
54-
k2
55-
[]
56-
[]
45+
## N-I bash STDOUT:
46+
declare -A assoc=([k1]="foo" [k2]="spam eggs" )
47+
declare -A assoc=()
5748
## END
5849

59-
#### Can't initialize assoc array with indexed array
50+
#### Can initialize assoc array with the "(key value ...)" sequence
6051
declare -A A=(1 2 3)
6152
echo status=$?
53+
declare -p A
6254
## STDOUT:
63-
status=2
55+
status=0
56+
declare -A A=(['1']=2 ['3']='')
6457
## END
6558

66-
# bash prints warnings to stderr but gives no indication of the problem
59+
# bash-4.4 prints warnings to stderr but gives no indication of the problem
6760
## BUG bash STDOUT:
6861
status=0
62+
declare -A A=()
6963
## END
7064

7165
#### create empty assoc array, put, then get

spec/array-literal.test.sh

+13
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,16 @@ declare -A s1=([b]="y" [a]="x" )
111111
status=0
112112
declare -A s2=([0]="world" [b]="y" [a]="x" )
113113
## END
114+
115+
#### assoc=(key value ...) is not allowed in the strict_array mode
116+
case $SH in bash) ;; *) shopt --set strict_array;; esac
117+
118+
declare -A a=([a]=b)
119+
eval "a=(1 2 3 4)"
120+
declare -p a
121+
## STDOUT:
122+
declare -A a=()
123+
## END
124+
## N-I bash STDOUT:
125+
declare -A a=([3]="4" [1]="2" )
126+
## END

yaks/preamble.h

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <errno.h>
44

5+
#include "_gen/core/optview.h" // added because bash_impl uses it, via j8
56
#include "_gen/core/value.asdl.h" // could break this dep from j8?
67
#include "_gen/data_lang/nil8.asdl.h"
78
#include "_gen/display/pretty.asdl.h"

0 commit comments

Comments
 (0)