Skip to content

Commit c3eee5f

Browse files
author
Andy C
committed
[osh/word_eval] Detect builtin declare s=foo, etc.
Reported by Koiche on Zulip. We also hit it several other times over the years. Also make 'builtin' a regular builtin. For some reason it was categorized as a specail one. One failing test case - our parsing of these is inconsistent: builtin declare a=(x y) builtin declare -a a=(x y)
1 parent 7f3c044 commit c3eee5f

File tree

4 files changed

+96
-29
lines changed

4 files changed

+96
-29
lines changed

frontend/builtin_def.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040

4141
'getopts',
4242

43-
# introspection
44-
'command', 'type', 'hash', 'help', 'history',
43+
# introspection / meta
44+
'builtin', 'command', 'type', 'hash', 'help', 'history',
4545

4646
'alias', 'unalias',
4747
'bind',
@@ -112,7 +112,7 @@ def _Init(b):
112112
b.Add('.', enum_name='dot', kind='special')
113113
# Python keyword
114114
b.Add('exec', enum_name='exec_', kind='special')
115-
for name in ['eval', 'set', 'shift', 'times', 'trap', 'unset', 'builtin']:
115+
for name in ['eval', 'set', 'shift', 'times', 'trap', 'unset']:
116116
b.Add(name, kind='special')
117117

118118
#

osh/word_eval.py

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
VarSubState,
4141
Piece,
4242
)
43-
from _devbuild.gen.option_asdl import option_i
43+
from _devbuild.gen.option_asdl import option_i, builtin_i
4444
from _devbuild.gen.value_asdl import (
4545
value,
4646
value_e,
@@ -2141,22 +2141,66 @@ def _EvalAssignBuiltin(self, builtin_id, arg0, words):
21412141

21422142
return cmd_value.Assign(builtin_id, flags, flag_locs, assign_args)
21432143

2144+
def _DetectAssignBuiltinStr(self, arg0, words):
2145+
# type: (str, List[CompoundWord]) -> Optional[cmd_value.Assign]
2146+
builtin_id = consts.LookupAssignBuiltin(arg0)
2147+
if builtin_id != consts.NO_INDEX:
2148+
return self._EvalAssignBuiltin(builtin_id, arg0, words)
2149+
return None
2150+
2151+
def _DetectAssignBuiltin(self, val0, words):
2152+
# type: (part_value_t, List[CompoundWord]) -> Optional[cmd_value.Assign]
2153+
UP_val0 = val0
2154+
if val0.tag() == part_value_e.String:
2155+
val0 = cast(Piece, UP_val0)
2156+
if not val0.quoted:
2157+
return self._DetectAssignBuiltinStr(val0.s, words)
2158+
return None
2159+
2160+
def _DetectMetaBuiltinStr(self, s):
2161+
# type: (str) -> bool
2162+
"""
2163+
We need to detect all of these cases:
2164+
2165+
builtin local
2166+
command local
2167+
builtin builtin local
2168+
builtin command local
2169+
2170+
Fundamentally, assignment builtins have different WORD EVALUATION RULES
2171+
for a=$x (no word splitting), so it seems hard to do this in
2172+
meta_osh.Builtin() or meta_osh.Command()
2173+
"""
2174+
return (consts.LookupNormalBuiltin(s)
2175+
in (builtin_i.builtin, builtin_i.command))
2176+
2177+
def _DetectMetaBuiltin(self, val0):
2178+
# type: (part_value_t) -> bool
2179+
UP_val0 = val0
2180+
if val0.tag() == part_value_e.String:
2181+
val0 = cast(Piece, UP_val0)
2182+
if not val0.quoted:
2183+
return self._DetectMetaBuiltinStr(val0.s)
2184+
return False
2185+
21442186
def SimpleEvalWordSequence2(self, words, allow_assign):
21452187
# type: (List[CompoundWord], bool) -> cmd_value_t
21462188
"""Simple word evaluation for YSH."""
21472189
strs = [] # type: List[str]
21482190
locs = [] # type: List[CompoundWord]
21492191

2192+
assign_builtin_offset = 0
21502193
for i, w in enumerate(words):
21512194
# No globbing in the first arg for command.Simple.
2152-
if i == 0 and allow_assign:
2195+
if i == assign_builtin_offset and allow_assign:
21532196
strs0 = self._EvalWordToArgv(w)
2197+
# Same logic as legacy word eval, with no splitting
2198+
# TODO: Remove this because we should remove assignment
2199+
# builtins? What about export?
21542200
if len(strs0) == 1:
2155-
arg0 = strs0[0]
2156-
builtin_id = consts.LookupAssignBuiltin(arg0)
2157-
if builtin_id != consts.NO_INDEX:
2158-
# Same logic as legacy word eval, with no splitting
2159-
return self._EvalAssignBuiltin(builtin_id, arg0, words)
2201+
cmd_val = self._DetectAssignBuiltinStr(strs0[0], words)
2202+
if cmd_val:
2203+
return cmd_val
21602204

21612205
strs.extend(strs0)
21622206
for _ in strs0:
@@ -2201,22 +2245,6 @@ def SimpleEvalWordSequence2(self, words, allow_assign):
22012245

22022246
return cmd_value.Argv(strs, locs, None, None, None, None)
22032247

2204-
def _DetectAssignBuiltinStr(self, arg0, words):
2205-
# type: (str, List[CompoundWord]) -> Optional[cmd_value.Assign]
2206-
builtin_id = consts.LookupAssignBuiltin(arg0)
2207-
if builtin_id != consts.NO_INDEX:
2208-
return self._EvalAssignBuiltin(builtin_id, arg0, words)
2209-
return None
2210-
2211-
def _DetectAssignBuiltin(self, val0, words):
2212-
# type: (part_value_t, List[CompoundWord]) -> Optional[cmd_value.Assign]
2213-
UP_val0 = val0
2214-
if val0.tag() == part_value_e.String:
2215-
val0 = cast(Piece, UP_val0)
2216-
if not val0.quoted:
2217-
return self._DetectAssignBuiltinStr(val0.s, words)
2218-
return None
2219-
22202248
def EvalWordSequence2(self, words, allow_assign=False):
22212249
# type: (List[CompoundWord], bool) -> cmd_value_t
22222250
"""Turns a list of Words into a list of strings.
@@ -2245,6 +2273,8 @@ def EvalWordSequence2(self, words, allow_assign=False):
22452273
strs = [] # type: List[str]
22462274
locs = [] # type: List[CompoundWord]
22472275

2276+
assign_builtin_offset = 0
2277+
22482278
n = 0
22492279
for i, w in enumerate(words):
22502280
fast_str = word_.FastStrEval(w)
@@ -2253,10 +2283,14 @@ def EvalWordSequence2(self, words, allow_assign=False):
22532283
locs.append(w)
22542284

22552285
# e.g. the 'local' in 'local a=b c=d' will be here
2256-
if allow_assign and i == 0:
2286+
if allow_assign and i == assign_builtin_offset:
22572287
cmd_val = self._DetectAssignBuiltinStr(fast_str, words)
22582288
if cmd_val:
22592289
return cmd_val
2290+
#print('YO %s' % fast_str)
2291+
if self._DetectMetaBuiltinStr(fast_str):
2292+
#print('META')
2293+
assign_builtin_offset += 1
22602294
continue
22612295

22622296
part_vals = [] # type: List[part_value_t]
@@ -2271,11 +2305,16 @@ def EvalWordSequence2(self, words, allow_assign=False):
22712305
#
22722306
# But we don't want to evaluate the first word twice in the case of:
22732307
# $(some-command) --flag
2274-
if allow_assign and i == 0 and len(part_vals) == 1:
2308+
if (allow_assign and i == assign_builtin_offset and
2309+
len(part_vals) == 1):
22752310
cmd_val = self._DetectAssignBuiltin(part_vals[0], words)
22762311
if cmd_val:
22772312
return cmd_val
22782313

2314+
if len(part_vals) == 1:
2315+
if self._DetectMetaBuiltin(part_vals[0]):
2316+
assign_builtin_offset += 1
2317+
22792318
if 0:
22802319
log('')
22812320
log('part_vals after _EvalWordToParts:')

spec/builtin-meta.test.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## oils_failures_allowed: 6
1+
## oils_failures_allowed: 1
22
## compare_shells: dash bash mksh zsh
33

44
#### command -v
@@ -515,3 +515,4 @@ cbr=readonly
515515
## END
516516
## N-I dash/zsh STDOUT:
517517
## END
518+

spec/builtin-special.test.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,30 @@ echo status=$?
112112
true func
113113
status=0
114114
## END
115+
116+
117+
#### command, builtin - both can be redefined, not special (regression)
118+
case $SH in dash) exit ;; esac
119+
120+
builtin echo b
121+
command echo c
122+
123+
builtin() {
124+
echo builtin-redef "$@"
125+
}
126+
127+
command() {
128+
echo command-redef "$@"
129+
}
130+
131+
builtin echo b
132+
command echo c
133+
134+
## STDOUT:
135+
b
136+
c
137+
builtin-redef echo b
138+
command-redef echo c
139+
## END
140+
## N-I dash STDOUT:
141+
## END

0 commit comments

Comments
 (0)