fix(ast): 修正实参位前导 * 的优先级,*p + n 解析为 (*p) + n

规范 rule 5 允许把 c.Deref(x) 写作 *x,但语法分析器在实参位置对前导 * 取整条表达式作为操作数:*buf + *length 被解析成 *(buf + *length),*length + data_len 成 *(length + data_len),多解引用一层。表现为 zlib decompressobj 全部失效(decompress 返回 0 字节、eof 恒 0、unused_data 取不到),requests/download 同类语句亦受影响。

改为:实参位 *expr 只按 _parse_factor 取紧绑定操作数,经新增 Parser.starred_seed 挂起,由 _parse_factor 顶部接手,再交由上层续接二元运算。*p[0](即 *(p[0]))、*p**2(即 *(p**2))等结合性保持原 C/Python 语义。

验证:全量 77/77 顺序回归一次通过;ZlibTest 内部断言由 74 passed / 6 failed 变为 79 passed / 0 failed。
This commit is contained in:
TermiNexus committed 2026-10-05 15:04:01 +08:00
1 parent e80284a6d5
commit 3e1ed0992b
1 file changed
+22 -3
+22 -3
View File
@@ -55,6 +55,8 @@ class Parser:
pool: rawptr[memhub.MemManager] # memory pool
error_count: int # error count
pending_decorators: rawptr[list[rawptr[AST]]] # pending decorators list
# 实参位前导 `*` 的已解析左操作数挂起槽(见 _parse_call / _parse_power)。
starred_seed: rawptr[AST]
# `static` 修饰符(软关键字)挂起标记:语句起始消费 `static`(后随变量声明
# `Name :`)后置 1,由紧随的 `_parse_type_ann` 在类型联合最左插入 t.CStatic
# 标记(`static x: int = 0` → `x: t.CStatic | int = 0`),插入后立即清零,
@@ -95,6 +97,7 @@ def _parser_init(ps: rawptr[Parser], tokens: rawptr[Token],
ps.pool = pool
ps.error_count = 0
ps.pending_decorators = None
ps.starred_seed = None
ps.pending_static = 0
ps.in_class_body = 0
ps.step_count = 0
@@ -1640,6 +1643,13 @@ def _parse_term(ps: rawptr[Parser]) -> rawptr[AST]:
def _parse_factor(ps: rawptr[Parser]) -> rawptr[AST]:
"""factor: '+' factor | '-' factor | '~' factor | '*' factor | '&' factor | power"""
# 实参位前导 `*` 已由 _parse_call 解析出紧绑定的 Starred 左操作数,直接作为本层
# 因子返回(必须先于下面的一元判定,否则其后的二元 `+`/`-` 会被误判为一元)。
# 上层各层随即以该左操作数续接二元运算:`*p + n` ⇒ `(*p) + n`,而非 `*(p + n)`。
if ps.starred_seed is not None:
seed: rawptr[AST] = ps.starred_seed
ps.starred_seed = None
return seed
if (_match_op(ps, TokOp.Plus) or _match_op(ps, TokOp.Minus) or _match_op(ps, TokOp.Tilde)
or _match_op(ps, TokOp.Star) or _match_op(ps, TokOp.Amp)):
op_token: int = _cur_op(ps)
@@ -1755,10 +1765,19 @@ def _parse_call(ps: rawptr[Parser], func: rawptr[AST]) -> rawptr[AST]:
elif _match_op(ps, TokOp.Star):
_advance(ps)
_skip_nls_and_newlines(ps)
# 只按一元因子取下界(保持 `*p[0]` = `*(p[0])` 的 C 结合),再挂到
# starred_seed 上,交由 _parse_expr 续接其后二元运算:`*p + n` ⇒
# `(*p) + n`。若整体用 _parse_expr,前导 `*` 会吞掉整条 `p + n`
# 而错解为 `*(p + n)`(zlib append_bytes 的 out 参数不回写根因)。
seed: rawptr[AST] = _parse_factor(ps)
if seed is None:
ps.error_count += 1
break
star: rawptr[AST] = Starred(ps.pool, seed, ASTCtx.Load)
_set_pos(star, seed.lineno, seed.col_offset, 0, 0)
ps.starred_seed = star
val = _parse_expr(ps)
star: rawptr[AST] = Starred(ps.pool, val, ASTCtx.Load)
_set_pos(star, val.lineno, val.col_offset, 0, 0)
args.append(star)
args.append(val)
# keyword = expr
elif _cur_type(ps) == TokenType.Name:
if ps.cur.next is not None and ps.cur.next.type == TokenType.Op and ps.cur.next.op_subtype == TokOp.Equal: