fix(phase1): 容器扫描覆盖表达式字段,修复 _list/_dict 按需注入失效

TPV 的 AST 中表达式/注解树挂在结构体字段(value/slice/func/args/
annotation/targets/returns/bases/left/right),只有语句块在 children。
原 _ScanTreeForContainers 只递归 children,漏掉 list[...] | t.CPtr
(BinOp 包裹)、list[...](...)(Call.func)、字段注解等,导致 base.vp
等 includes 的 list/dict 使用检测不到,_list 不进可达集合,泛型模板
未注册。重写为字段递归 + children 递归,覆盖全部表达式承载点。
This commit is contained in:
TermiNexus committed 2026-08-13 04:33:18 +08:00
1 parent dfb86380a6
commit f09b6962f9
1 file changed
+102 -7
+102 -7
View File
@@ -229,14 +229,17 @@ def _FindClassDefSha1(class_names_buf: bytes, def_sha1s_buf: bytes,
# ============================================================
# _ScanTreeForContainers - 递归扫描 AST 检测 list/dict 容器使用
#
# 覆盖:裸名(list/dict)、泛型下标(list[T] 的 Subscript.value)、
# 字面量([]/{})。命中即置 need_list/need_dict 标志。
# 误报无害(最多多编译 _list/_dict 两个空壳模块)。
# TPV 的 AST 中,语句块挂在 children,而表达式/注解树全部以结构体
# 字段挂载(value/slice/func/args/annotation/targets/returns/bases/
# left/right 等)。只递归 children 会漏掉 list[...] | t.CPtr(BinOp
# 包裹)、list[...](...)(Call.func)、字段注解等一切非语句块位置。
# 因此每个节点先递归其表达式承载字段,再递归 children 语句块。
# 命中即置 need_list/need_dict;误报无害(最多多编译两个空壳模块)。
# ============================================================
def _ScanTreeForContainers(node: ast.AST | t.CPtr,
need_list_box: t.CPtr,
need_dict_box: t.CPtr):
"""递归扫描 AST,检测 list/dict 容器使用"""
"""递归扫描 AST(字段 + 语句块),检测 list/dict 容器使用"""
if node is None:
return
k_s: int = node.kind()
@@ -249,15 +252,107 @@ def _ScanTreeForContainers(node: ast.AST | t.CPtr,
need_dict_box[0] = 1
elif k_s == ast.ASTKind.Subscript:
sub_s: ast.Subscript | t.CPtr = (ast.Subscript | t.CPtr)(node)
if sub_s.value is not None:
_ScanTreeForContainers(sub_s.value, need_list_box, need_dict_box)
_ScanTreeForContainers(sub_s.value, need_list_box, need_dict_box)
_ScanTreeForContainers(sub_s.slice, need_list_box, need_dict_box)
elif k_s == ast.ASTKind.BinOp:
bop_s: ast.BinOp | t.CPtr = (ast.BinOp | t.CPtr)(node)
_ScanTreeForContainers(bop_s.left, need_list_box, need_dict_box)
_ScanTreeForContainers(bop_s.right, need_list_box, need_dict_box)
elif k_s == ast.ASTKind.Call:
cl_s2: ast.Call | t.CPtr = (ast.Call | t.CPtr)(node)
_ScanTreeForContainers(cl_s2.func, need_list_box, need_dict_box)
cl_args_s: list[ast.AST | t.CPtr] | t.CPtr = cl_s2.args
if cl_args_s is not None:
na_s: t.CSizeT = len(cl_args_s)
i_a2: t.CSizeT
for i_a2 in range(na_s):
_ScanTreeForContainers(cl_args_s.get(i_a2), need_list_box, need_dict_box)
cl_kws_s: list[ast.AST | t.CPtr] | t.CPtr = cl_s2.keywords
if cl_kws_s is not None:
nk_s: t.CSizeT = len(cl_kws_s)
i_k2: t.CSizeT
for i_k2 in range(nk_s):
kw_s: ast.Keyword | t.CPtr = (ast.Keyword | t.CPtr)(cl_kws_s.get(i_k2))
if kw_s is not None:
_ScanTreeForContainers(kw_s.value, need_list_box, need_dict_box)
elif k_s == ast.ASTKind.Assign:
asn_s: ast.Assign | t.CPtr = (ast.Assign | t.CPtr)(node)
_ScanTreeForContainers(asn_s.value, need_list_box, need_dict_box)
tgts_s: list[ast.AST | t.CPtr] | t.CPtr = asn_s.targets
if tgts_s is not None:
nt_s: t.CSizeT = len(tgts_s)
i_t2: t.CSizeT
for i_t2 in range(nt_s):
_ScanTreeForContainers(tgts_s.get(i_t2), need_list_box, need_dict_box)
elif k_s == ast.ASTKind.AnnAssign:
aa_s2: ast.AnnAssign | t.CPtr = (ast.AnnAssign | t.CPtr)(node)
_ScanTreeForContainers(aa_s2.annotation, need_list_box, need_dict_box)
_ScanTreeForContainers(aa_s2.value, need_list_box, need_dict_box)
elif k_s == ast.ASTKind.FunctionDef:
fn_s: ast.FunctionDef | t.CPtr = (ast.FunctionDef | t.CPtr)(node)
_ScanTreeForContainers(fn_s.returns, need_list_box, need_dict_box)
fn_args_s: ast.Arguments | t.CPtr = fn_s.args
if fn_args_s is not None:
fa_s: list[ast.AST | t.CPtr] | t.CPtr = fn_args_s.args
if fa_s is not None:
nf_s: t.CSizeT = len(fa_s)
i_f2: t.CSizeT
for i_f2 in range(nf_s):
arg_s: ast.Arg | t.CPtr = (ast.Arg | t.CPtr)(fa_s.get(i_f2))
if arg_s is not None:
_ScanTreeForContainers(arg_s.annotation, need_list_box, need_dict_box)
elif k_s == ast.ASTKind.ClassDef:
cd_s: ast.ClassDef | t.CPtr = (ast.ClassDef | t.CPtr)(node)
bases_s: list[ast.AST | t.CPtr] | t.CPtr = cd_s.bases
if bases_s is not None:
nb_s: t.CSizeT = len(bases_s)
i_b2: t.CSizeT
for i_b2 in range(nb_s):
_ScanTreeForContainers(bases_s.get(i_b2), need_list_box, need_dict_box)
elif k_s == ast.ASTKind.Attribute:
at_s: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(node)
_ScanTreeForContainers(at_s.value, need_list_box, need_dict_box)
elif k_s == ast.ASTKind.Tuple:
tu_s: ast.Tuple | t.CPtr = (ast.Tuple | t.CPtr)(node)
elts_s: list[ast.AST | t.CPtr] | t.CPtr = tu_s.elts
if elts_s is not None:
ne_s: t.CSizeT = len(elts_s)
i_e2: t.CSizeT
for i_e2 in range(ne_s):
_ScanTreeForContainers(elts_s.get(i_e2), need_list_box, need_dict_box)
elif k_s == ast.ASTKind.UnaryOp:
uop_s: ast.UnaryOp | t.CPtr = (ast.UnaryOp | t.CPtr)(node)
_ScanTreeForContainers(uop_s.operand, need_list_box, need_dict_box)
elif k_s == ast.ASTKind.Expr:
ex_s: ast.Expr | t.CPtr = (ast.Expr | t.CPtr)(node)
_ScanTreeForContainers(ex_s.value, need_list_box, need_dict_box)
elif k_s == ast.ASTKind.Return:
rt_s: ast.Return | t.CPtr = (ast.Return | t.CPtr)(node)
_ScanTreeForContainers(rt_s.value, need_list_box, need_dict_box)
elif k_s == ast.ASTKind.Compare:
cp_s: ast.Compare | t.CPtr = (ast.Compare | t.CPtr)(node)
_ScanTreeForContainers(cp_s.left, need_list_box, need_dict_box)
cps_s: list[ast.AST | t.CPtr] | t.CPtr = cp_s.comparators
if cps_s is not None:
nc_s: t.CSizeT = len(cps_s)
i_c2: t.CSizeT
for i_c2 in range(nc_s):
_ScanTreeForContainers(cps_s.get(i_c2), need_list_box, need_dict_box)
elif k_s == ast.ASTKind.BoolOp:
bo_s: ast.BoolOp | t.CPtr = (ast.BoolOp | t.CPtr)(node)
bvs_s: list[ast.AST | t.CPtr] | t.CPtr = bo_s.values
if bvs_s is not None:
nv_s: t.CSizeT = len(bvs_s)
i_v2: t.CSizeT
for i_v2 in range(nv_s):
_ScanTreeForContainers(bvs_s.get(i_v2), need_list_box, need_dict_box)
elif k_s == ast.ASTKind.List:
# [] 字面量
need_list_box[0] = 1
elif k_s == ast.ASTKind.Dict:
# {} 字面量
need_dict_box[0] = 1
# 递归子节点
# 递归语句块子节点
if node.children is not None:
ch_s: list[ast.AST | t.CPtr] | t.CPtr = node.children
n_s: t.CSizeT = len(ch_s)