Skip to content

Commit b363822

Browse files
authored
[osh] Promote a string to an array with str+=(...) (#2283)
* [osh/cmd_eval] Disallow string promotion with +=() with strict_array * [osh/cmd_eval] Use tagswitch
1 parent ee7f8d3 commit b363822

File tree

5 files changed

+108
-36
lines changed

5 files changed

+108
-36
lines changed

builtin/assign_osh.py

+29-13
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from display import ui
2121
from frontend import flag_util
2222
from frontend import args
23-
from mycpp.mylib import log
23+
from mycpp.mylib import log, tagswitch
2424
from osh import cmd_eval
2525
from osh import sh_expr_eval
2626
from data_lang import j8_lite
@@ -242,20 +242,36 @@ def _AssignVarForBuiltin(mem, rval, pair, which_scopes, flags, arith_ev,
242242
"Can't convert type %s into BashArray" %
243243
ui.ValType(old_val), pair.blame_word)
244244
elif flag_A:
245-
if old_val.tag() in (value_e.Undef, value_e.Str):
246-
# Note: We explicitly initialize BashAssoc for Undef.
247-
val = bash_impl.BashAssoc_New()
248-
elif old_val.tag() == value_e.BashAssoc:
249-
# We do not need adjustments for -A.
250-
pass
251-
else:
252-
# Note: BashArray cannot be converted to a BashAssoc
253-
e_die(
254-
"Can't convert type %s into BashAssoc" %
255-
ui.ValType(old_val), pair.blame_word)
245+
with tagswitch(old_val) as case:
246+
if case(value_e.Undef):
247+
# Note: We explicitly initialize BashAssoc for Undef.
248+
val = bash_impl.BashAssoc_New()
249+
elif case(value_e.Str):
250+
# Note: We explicitly initialize BashAssoc for Str. When
251+
# applying +=() to Str, we associate an old value to the
252+
# key '0'. OSH disables this when strict_array is turned
253+
# on.
254+
assoc_val = bash_impl.BashAssoc_New()
255+
if pair.plus_eq:
256+
if mem.exec_opts.strict_array():
257+
e_die(
258+
"Can't convert Str to BashAssoc (strict_array)",
259+
pair.blame_word)
260+
bash_impl.BashAssoc_SetElement(
261+
assoc_val, '0',
262+
cast(value.Str, old_val).s)
263+
val = assoc_val
264+
elif case(value_e.BashAssoc):
265+
# We do not need adjustments for -A.
266+
pass
267+
else:
268+
# Note: BashArray cannot be converted to a BashAssoc
269+
e_die(
270+
"Can't convert type %s into BashAssoc" %
271+
ui.ValType(old_val), pair.blame_word)
256272

257273
val = cmd_eval.ListInitializeTarget(val, initializer, pair.plus_eq,
258-
pair.blame_word)
274+
mem.exec_opts, pair.blame_word)
259275
elif pair.plus_eq:
260276
old_val = sh_expr_eval.OldValue(
261277
lval,

doc/ref/chap-osh-assign.md

+4-6
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,7 @@ In the first phase, the type adjustment is performed in the following way:
8181
element, where the original value is stored at index `0`. If the assignment
8282
is performed through an assignment builtin and flag `-A` is supplied to the
8383
builtin, the assignment creates a BashAssoc with one element, where the
84-
original value is stored at key `"0"`, instead of a BashArray. If the
85-
assignment operator is `+=`, OSH issues an error "Can't append an array to
86-
string", while Bash is permissive.
84+
original value is stored at key `"0"`, instead of a BashArray.
8785
- When the LHS is an indexed or associative arrays, the original array is
8886
directly used for the modification target. If the
8987
assignment is performed through an assignment builtin and mismatching flag
@@ -121,17 +119,17 @@ These rules are summarized in the following table.
121119
- Str
122120
- (none)
123121
- BashArray with one element, with the original string at index 0
124-
- OSH does not accept `+=`, so the element is never used
122+
- Error with `strict_array`
125123
- tr
126124
- <!-- empty -->
127125
- `-a`
128126
- BashArray with one element, with the original string at index 0
129-
- OSH does not accept `+=`, so the element is never used
127+
- Error with `strict_array`
130128
- tr
131129
- <!-- empty -->
132130
- `-A`
133131
- BashAssoc with one element, with the original string at key `"0"`
134-
- OSH does not accept `+=`, so the element is never used
132+
- Error with `strict_array`
135133
- tr
136134
- BashArray
137135
- (none)

osh/cmd_eval.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -193,18 +193,23 @@ def _HasManyStatuses(node):
193193
def ListInitializeTarget(old_val,
194194
initializer,
195195
has_plus,
196+
exec_opts,
196197
blame_loc,
197198
destructive=True):
198-
# type: (value_t, value.InitializerList, bool, loc_t, bool) -> value_t
199+
# type: (value_t, value.InitializerList, bool, optview.Exec, loc_t, bool) -> value_t
199200
UP_old_val = old_val
200201
with tagswitch(old_val) as case:
201202
if case(value_e.Undef):
202203
return bash_impl.BashArray_New()
203204
elif case(value_e.Str):
204205
if has_plus:
205-
e_die("Can't append array to string")
206-
207-
return bash_impl.BashArray_New()
206+
if exec_opts.strict_array():
207+
e_die("Can't convert Str to BashArray (strict_array)",
208+
blame_loc)
209+
old_val = cast(value.Str, UP_old_val)
210+
return bash_impl.BashArray_FromList([old_val.s])
211+
else:
212+
return bash_impl.BashArray_New()
208213
elif case(value_e.BashArray):
209214
old_val = cast(value.BashArray, UP_old_val)
210215
if not destructive:
@@ -624,6 +629,7 @@ def _EvalTempEnv(self, more_env, flags):
624629
val = ListInitializeTarget(old_val,
625630
initializer,
626631
has_plus,
632+
self.exec_opts,
627633
e_pair.left,
628634
destructive=False)
629635

@@ -1040,7 +1046,7 @@ def _DoShAssignment(self, node, cmd_st):
10401046
node.left)
10411047

10421048
val = ListInitializeTarget(old_val, initializer, has_plus,
1043-
pair.left)
1049+
self.exec_opts, pair.left)
10441050

10451051
elif has_plus:
10461052
# do not respect set -u

spec/append.test.sh

+10-12
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,11 @@ argv.py "${y[@]}"
4848
#### error: s+=(my array)
4949
s='abc'
5050
s+=(d e f)
51-
echo $s
52-
## status: 1
53-
## stdout-json: ""
54-
## BUG bash/mksh status: 0
55-
## BUG bash/mksh stdout: abc
56-
## OK zsh status: 0
57-
## OK zsh stdout: abc d e f
51+
argv.py "${s[@]}"
52+
## status: 0
53+
## STDOUT:
54+
['abc', 'd', 'e', 'f']
55+
## END
5856

5957
#### error: myarray+=s
6058

@@ -78,16 +76,16 @@ typeset s+=(d e f)
7876
echo status=$?
7977
argv.py "${s[@]}"
8078

81-
## status: 1
79+
## status: 0
8280
## STDOUT:
8381
abc
84-
## END
85-
## OK bash status: 0
86-
## OK bash STDOUT:
87-
abc
8882
status=0
8983
['abc', 'd', 'e', 'f']
9084
## END
85+
## N-I mksh/zsh status: 1
86+
## N-I mksh/zsh stdout: abc
87+
## N-I mksh stderr: mksh: <stdin>[4]: syntax error: '(' unexpected
88+
## N-I zsh stderr: typeset: not valid in this context: s+
9189

9290
#### error: typeset myarray+=s
9391
typeset a=(x y)

spec/array-literal.test.sh

+54
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,57 @@ status=1
5757
status=1
5858
['x', 'y', 'z']
5959
## END
60+
61+
#### s+=() with strict_array
62+
case $SH in bash) ;; *) shopt --set strict_array;; esac
63+
64+
s1=hello
65+
s2=world
66+
67+
# Overwriting Str with a new BashArray is allowed
68+
eval 's1=(1 2 3 4)'
69+
echo status=$?
70+
declare -p s1
71+
# Promoting Str to a BashArray is disallowed
72+
eval 's2+=(1 2 3 4)'
73+
echo status=$?
74+
declare -p s2
75+
## STDOUT:
76+
status=0
77+
declare -a s1=(1 2 3 4)
78+
status=1
79+
declare -- s2=world
80+
## END
81+
## N-I bash STDOUT:
82+
status=0
83+
declare -a s1=([0]="1" [1]="2" [2]="3" [3]="4")
84+
status=0
85+
declare -a s2=([0]="world" [1]="1" [2]="2" [3]="3" [4]="4")
86+
## END
87+
88+
#### declare -A s+=() with strict_array
89+
case $SH in bash) ;; *) shopt --set strict_array;; esac
90+
91+
s1=hello
92+
s2=world
93+
94+
# Overwriting Str with a new BashAssoc is allowed
95+
eval 'declare -A s1=([a]=x [b]=y)'
96+
echo status=$?
97+
declare -p s1
98+
# Promoting Str to a BashAssoc is disallowed
99+
eval 'declare -A s2+=([a]=x [b]=y)'
100+
echo status=$?
101+
declare -p s2
102+
## STDOUT:
103+
status=0
104+
declare -A s1=(['a']=x ['b']=y)
105+
status=1
106+
declare -- s2=world
107+
## END
108+
## N-I bash STDOUT:
109+
status=0
110+
declare -A s1=([b]="y" [a]="x" )
111+
status=0
112+
declare -A s2=([0]="world" [b]="y" [a]="x" )
113+
## END

0 commit comments

Comments
 (0)