debug(stub): 补全 [DBG-P7] 插桩覆盖所有票名产生路径(定位后删除)
在 96c1102 基础上把"可能产出该票名"的路径一次打全,保证一次重建即可定死根因:
- TypeGenResolve._class_true_def_sha1:MISS 时额外 dump 结构体表里所有名字含
ClassDef 的条目(下标/名字/归属 sha1/Ty 的 IR 名前缀)——直接看出别名 entry
到底有没有、挂在哪个 sha1 上。
- TypeGenResolve.extract_class_name_from_annotation:AnnotClassName 通道算出的
泛型票名(含 ClassDef 才打)。
- TypeGenResolve._resolve_generic_subscript_ty:注解/字段侧算出的泛型特化名
gen_mangled(含 ClassDef 才打)。
- CallGenCtor._extract_type_args_from_slice:构造函数侧实参串的 raw/norm 两态。
- VpiReader._inject_class_struct_declare:进入 + 各出口
(already / renum_skip / field_unresolved / zero_fields / registered)。
- VpiReader._inject_reexport_class_declare:调用现场与四个出口
(already / no_src_entry / alias_reg_fail / alias_ok)。
This commit is contained in:
1 parent
96c110242e
commit
3b8a2b225f
3 files changed
+66
-6
No files matched your search
@@ -2638,7 +2638,9 @@ def _inject_reexport_class_declare(pool: memhub.MemBuddy*,
|
|||||||
# [DBG-P7] 临时诊断(定位 ast.ClassDef 票名前缀分裂后删除):只盯 ClassDef,
|
# [DBG-P7] 临时诊断(定位 ast.ClassDef 票名前缀分裂后删除):只盯 ClassDef,
|
||||||
# 打印"源 entry 是否已注册 / 别名是否已存在"——src_entry=0 即别名必然建不起来
|
# 打印"源 entry 是否已注册 / 别名是否已存在"——src_entry=0 即别名必然建不起来
|
||||||
# (上游:子模块 vpi 未注入或该类未被注册)。
|
# (上游:子模块 vpi 未注入或该类未被注册)。
|
||||||
|
_dbg_p7: int = 0
|
||||||
if string.strcmp(local_name, "ClassDef") == 0 or string.strcmp(src_name, "ClassDef") == 0:
|
if string.strcmp(local_name, "ClassDef") == 0 or string.strcmp(src_name, "ClassDef") == 0:
|
||||||
|
_dbg_p7 = 1
|
||||||
_se_dbg: GenTypes.StructEntry* = GenApi.find_struct_by_module(src_name, sub_sha1)
|
_se_dbg: GenTypes.StructEntry* = GenApi.find_struct_by_module(src_name, sub_sha1)
|
||||||
_se_ok_dbg: int = 0
|
_se_ok_dbg: int = 0
|
||||||
if _se_dbg is not None and _se_dbg.Ty is not None:
|
if _se_dbg is not None and _se_dbg.Ty is not None:
|
||||||
@@ -2650,6 +2652,8 @@ def _inject_reexport_class_declare(pool: memhub.MemBuddy*,
|
|||||||
src_name, local_name, sub_sha1, pkg_sha1, _se_ok_dbg, _al_dbg)
|
src_name, local_name, sub_sha1, pkg_sha1, _se_ok_dbg, _al_dbg)
|
||||||
# 幂等:包 sha1 下 local_name 已注册则跳过(重复 import 不重复注册)
|
# 幂等:包 sha1 下 local_name 已注册则跳过(重复 import 不重复注册)
|
||||||
if GenApi.find_struct_by_module(local_name, pkg_sha1) is not None:
|
if GenApi.find_struct_by_module(local_name, pkg_sha1) is not None:
|
||||||
|
if _dbg_p7 != 0:
|
||||||
|
stdio.printf("[DBG-P7] reexport-res src=%s res=already\n", src_name)
|
||||||
return 1
|
return 1
|
||||||
# 找子模块源 entry(仅类有 entry;函数/常量查不到自然跳过)
|
# 找子模块源 entry(仅类有 entry;函数/常量查不到自然跳过)
|
||||||
src_entry: GenTypes.StructEntry* = GenApi.find_struct_by_module(src_name, sub_sha1)
|
src_entry: GenTypes.StructEntry* = GenApi.find_struct_by_module(src_name, sub_sha1)
|
||||||
@@ -2660,11 +2664,18 @@ def _inject_reexport_class_declare(pool: memhub.MemBuddy*,
|
|||||||
if _ensure_dep_vpi_injected(pool, mod, sub_sha1, trans) != 0:
|
if _ensure_dep_vpi_injected(pool, mod, sub_sha1, trans) != 0:
|
||||||
src_entry = GenApi.find_struct_by_module(src_name, sub_sha1)
|
src_entry = GenApi.find_struct_by_module(src_name, sub_sha1)
|
||||||
if src_entry is None or src_entry.Ty is None:
|
if src_entry is None or src_entry.Ty is None:
|
||||||
|
if _dbg_p7 != 0:
|
||||||
|
stdio.printf("[DBG-P7] reexport-res src=%s sub=%s res=no_src_entry\n", src_name, sub_sha1)
|
||||||
return 0
|
return 0
|
||||||
# 注册别名 entry(绕过 register_struct 的 Ty 去重)
|
# 注册别名 entry(绕过 register_struct 的 Ty 去重)
|
||||||
if GenApi.register_struct_alias(pool, local_name, pkg_sha1, src_entry) is None:
|
if GenApi.register_struct_alias(pool, local_name, pkg_sha1, src_entry) is None:
|
||||||
|
if _dbg_p7 != 0:
|
||||||
|
stdio.printf("[DBG-P7] reexport-res src=%s res=alias_reg_fail\n", src_name)
|
||||||
return 0
|
return 0
|
||||||
GenApi.add_visible_struct(pool, local_name)
|
GenApi.add_visible_struct(pool, local_name)
|
||||||
|
if _dbg_p7 != 0:
|
||||||
|
stdio.printf("[DBG-P7] reexport-res src=%s local=%s pkg=%s res=alias_ok\n",
|
||||||
|
src_name, local_name, pkg_sha1)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@@ -2791,6 +2802,12 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
|
|||||||
flat_buf_c[p_len] = '_'
|
flat_buf_c[p_len] = '_'
|
||||||
string.strcpy(flat_buf_c + p_len + 1, cls_name)
|
string.strcpy(flat_buf_c + p_len + 1, cls_name)
|
||||||
real_name = flat_buf_c
|
real_name = flat_buf_c
|
||||||
|
# [DBG-P7] 临时诊断(定位 ast.ClassDef 票名前缀分裂后删除):只盯 ClassDef,
|
||||||
|
# 看注入期到底有没有把该类注册进来、在哪一步退出(字段解析失败/零字段/REnum)。
|
||||||
|
_dbg_p7c: int = 0
|
||||||
|
if string.strstr(real_name, "ClassDef") is not None:
|
||||||
|
_dbg_p7c = 1
|
||||||
|
stdio.printf("[DBG-P7] cls_inject name=%s src=%s\n", real_name, src_sha1)
|
||||||
# REPL(ReplStubLayoutMode,见文件末"REPL:includes 类完整布局与虚表恢复"):
|
# REPL(ReplStubLayoutMode,见文件末"REPL:includes 类完整布局与虚表恢复"):
|
||||||
# 本类若已由 .stub.ll 驱动的路径接管 ⇒ 这里整类让位——已登记(或本遍就地
|
# 本类若已由 .stub.ll 驱动的路径接管 ⇒ 这里整类让位——已登记(或本遍就地
|
||||||
# 登记成功)只标可见;父类未就绪 ⇒ 本遍不登记(收尾遍补齐);布局不可信 ⇒
|
# 登记成功)只标可见;父类未就绪 ⇒ 本遍不登记(收尾遍补齐);布局不可信 ⇒
|
||||||
@@ -2810,6 +2827,8 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
|
|||||||
existing_c: GenTypes.StructEntry* = GenApi.find_struct_by_module(real_name, src_sha1)
|
existing_c: GenTypes.StructEntry* = GenApi.find_struct_by_module(real_name, src_sha1)
|
||||||
if existing_c is not None and existing_c.Ty is not None:
|
if existing_c is not None and existing_c.Ty is not None:
|
||||||
GenApi.add_visible_struct(pool, real_name)
|
GenApi.add_visible_struct(pool, real_name)
|
||||||
|
if _dbg_p7c != 0:
|
||||||
|
stdio.printf("[DBG-P7] cls_inject res=already src=%s\n", src_sha1)
|
||||||
return 1
|
return 1
|
||||||
# REnum 类(class X(t.REnum))跳过:布局 {i32 __tag, <payload>} 由
|
# REnum 类(class X(t.REnum))跳过:布局 {i32 __tag, <payload>} 由
|
||||||
# ClassGenEnum._translate_renum_def 按变体计算;vpi 侧按普通 struct 注册
|
# ClassGenEnum._translate_renum_def 按变体计算;vpi 侧按普通 struct 注册
|
||||||
@@ -2817,6 +2836,8 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
|
|||||||
# 留给源模块正式翻译注册。LLVMType 本就因 field_count==0 早退,此守卫把
|
# 留给源模块正式翻译注册。LLVMType 本就因 field_count==0 早退,此守卫把
|
||||||
# 该行为显式化,避免将来 REnum 带直接字段时被误注册为普通 struct。
|
# 该行为显式化,避免将来 REnum 带直接字段时被误注册为普通 struct。
|
||||||
if _vpi_class_is_renum(class_cd) != 0:
|
if _vpi_class_is_renum(class_cd) != 0:
|
||||||
|
if _dbg_p7c != 0:
|
||||||
|
stdio.printf("[DBG-P7] cls_inject res=renum_skip src=%s\n", src_sha1)
|
||||||
return 0
|
return 0
|
||||||
# 规则 5.5 的证据前置(**必须早于**下方字段注解解析):
|
# 规则 5.5 的证据前置(**必须早于**下方字段注解解析):
|
||||||
# 本函数在**注入期**解析字段注解(GenApi.resolve_annotation_type →
|
# 本函数在**注入期**解析字段注解(GenApi.resolve_annotation_type →
|
||||||
@@ -2873,6 +2894,9 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
|
|||||||
pool, m_aa_c.annotation, imported_modules, from_imports, trans, 0)
|
pool, m_aa_c.annotation, imported_modules, from_imports, trans, 0)
|
||||||
if field_ty_c is None:
|
if field_ty_c is None:
|
||||||
# 字段类型解析失败:不注册该类(留给源模块编译注册),不 fatal
|
# 字段类型解析失败:不注册该类(留给源模块编译注册),不 fatal
|
||||||
|
if _dbg_p7c != 0:
|
||||||
|
stdio.printf("[DBG-P7] cls_inject res=field_unresolved field=%s src=%s\n",
|
||||||
|
m_tgt_c.id, src_sha1)
|
||||||
return 0
|
return 0
|
||||||
if field_count < GenTypes.FIELD_MAX:
|
if field_count < GenTypes.FIELD_MAX:
|
||||||
field_names_buf[field_count] = sizet(m_tgt_c.id)
|
field_names_buf[field_count] = sizet(m_tgt_c.id)
|
||||||
@@ -2881,6 +2905,8 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
|
|||||||
# 3. vpi 存根不含类字段(只序列化 class 名 + def 签名)→ field_count 恒为 0;注册空类会让源模块编译时 find_struct_by_module 命中空类
|
# 3. vpi 存根不含类字段(只序列化 class 名 + def 签名)→ field_count 恒为 0;注册空类会让源模块编译时 find_struct_by_module 命中空类
|
||||||
# → "结构体已注册但字段数为0" InternalCompilerError。故改为不注册:依赖模块已编译时命中真实类(上方已处理),未编译时由 _deps_pending 多遍收敛。
|
# → "结构体已注册但字段数为0" InternalCompilerError。故改为不注册:依赖模块已编译时命中真实类(上方已处理),未编译时由 _deps_pending 多遍收敛。
|
||||||
if field_count == 0:
|
if field_count == 0:
|
||||||
|
if _dbg_p7c != 0:
|
||||||
|
stdio.printf("[DBG-P7] cls_inject res=zero_fields src=%s\n", src_sha1)
|
||||||
return 0
|
return 0
|
||||||
# 4. 构建 ParamNode 链表
|
# 4. 构建 ParamNode 链表
|
||||||
first_node_c: rawptr[llvmlite.ParamNode] = None
|
first_node_c: rawptr[llvmlite.ParamNode] = None
|
||||||
@@ -2952,6 +2978,8 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
|
|||||||
af_ty_c: llvmlite.LLVMType* = (llvmlite.LLVMType*)(field_types_buf[afi_c])
|
af_ty_c: llvmlite.LLVMType* = (llvmlite.LLVMType*)(field_types_buf[afi_c])
|
||||||
GenApi.add_field(pool, entry_c, af_name_c, af_ty_c)
|
GenApi.add_field(pool, entry_c, af_name_c, af_ty_c)
|
||||||
GenApi.add_visible_struct(pool, real_name)
|
GenApi.add_visible_struct(pool, real_name)
|
||||||
|
if _dbg_p7c != 0:
|
||||||
|
stdio.printf("[DBG-P7] cls_inject res=registered src=%s fields=%d\n", src_sha1, field_count)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,7 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
|
|||||||
# (与 _generate_before_init 一致;translate_value 会把列表字面量
|
# (与 _generate_before_init 一致;translate_value 会把列表字面量
|
||||||
# 误译成动态 list 对象指针,coerce 到 [N x elem] 会失败)。
|
# 误译成动态 list 对象指针,coerce 到 [N x elem] 会失败)。
|
||||||
# Mixin 静态绑定:跨 Mixin 方法必须走 HandlesAnnAssign 模块级委托。
|
# Mixin 静态绑定:跨 Mixin 方法必须走 HandlesAnnAssign 模块级委托。
|
||||||
if fe.Ty is not None and self.Gen.ExprGenBase.is_array_type(fe.Ty) != 0 \
|
if fe.Ty is not None and self.Gen.ExprGenBase.is_array_type(fe.Ty) != 0 and fe.DefaultVal.kind() == ast.ASTKind.List:
|
||||||
and fe.DefaultVal.kind() == ast.ASTKind.List:
|
|
||||||
field_ptr_arr2: llvmlite.Value* = llvmlite.build_gep_struct(builder, struct_ty, fe.Ty, tmp, fe.Index)
|
field_ptr_arr2: llvmlite.Value* = llvmlite.build_gep_struct(builder, struct_ty, fe.Ty, tmp, fe.Index)
|
||||||
if field_ptr_arr2 is not None:
|
if field_ptr_arr2 is not None:
|
||||||
zero_arr2: llvmlite.Value* = llvmlite.ConstZero(pool, fe.Ty)
|
zero_arr2: llvmlite.Value* = llvmlite.ConstZero(pool, fe.Ty)
|
||||||
@@ -770,8 +769,14 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
|
|||||||
result.append(tn)
|
result.append(tn)
|
||||||
else:
|
else:
|
||||||
tn_raw = self.Gen.TypeGenResolve._get_type_name_from_annotation(pool, slice_node)
|
tn_raw = self.Gen.TypeGenResolve._get_type_name_from_annotation(pool, slice_node)
|
||||||
|
# [DBG-P7] 临时诊断(定位 ast.ClassDef 票名前缀分裂后删除):构造函数侧
|
||||||
|
# (`list[ast.ClassDef*](...)`)的实参归一,与注解侧的 gen_mangled 对比。
|
||||||
|
if tn_raw is not None and string.strstr(tn_raw, "ClassDef") is not None:
|
||||||
|
stdio.printf("[DBG-P7] ctor_arg raw=%s\n", tn_raw)
|
||||||
tn = self.Gen.TypeGenResolve._norm_type_arg_name(pool, tn_raw, from_imports)
|
tn = self.Gen.TypeGenResolve._norm_type_arg_name(pool, tn_raw, from_imports)
|
||||||
if tn is not None:
|
if tn is not None:
|
||||||
|
if string.strstr(tn, "ClassDef") is not None:
|
||||||
|
stdio.printf("[DBG-P7] ctor_arg norm=%s\n", tn)
|
||||||
result.append(tn)
|
result.append(tn)
|
||||||
return result
|
return result
|
||||||
# 泛型类构造调用辅助 list[str](pool, 8):复用 translate_list_literal 的构造逻辑——alloca tmp → 翻译 cl.args 为参数指针数组 → __new__(tmp,*args) → __before_init__ → __init__ → 返回 storage_ptr。必须独立于 translate_call 普通函数路径:spec_name 是类名不是函数名,走裸名会被 _lookup_mod_sha1 的 star import 回退误导
|
# 泛型类构造调用辅助 list[str](pool, 8):复用 translate_list_literal 的构造逻辑——alloca tmp → 翻译 cl.args 为参数指针数组 → __new__(tmp,*args) → __before_init__ → __init__ → 返回 storage_ptr。必须独立于 translate_call 普通函数路径:spec_name 是类名不是函数名,走裸名会被 _lookup_mod_sha1 的 star import 回退误导
|
||||||
@@ -854,13 +859,11 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
|
|||||||
# 与下方 Attribute 分支的 strstr 匹配一致:from t import CInt
|
# 与下方 Attribute 分支的 strstr 匹配一致:from t import CInt
|
||||||
# 后 `CInt(0)` 的 func 是 Name("CInt"),仅精确匹配 int/float
|
# 后 `CInt(0)` 的 func 是 Name("CInt"),仅精确匹配 int/float
|
||||||
# 会漏判 → fail-fast 误杀(VectorGenericTest 隐式泛型推断)。
|
# 会漏判 → fail-fast 误杀(VectorGenericTest 隐式泛型推断)。
|
||||||
if string.strcmp(fn_nm.id, "float") == 0 or string.strcmp(fn_nm.id, "double") == 0 \
|
if string.strcmp(fn_nm.id, "float") == 0 or string.strcmp(fn_nm.id, "double") == 0 or string.strstr(fn_nm.id, "Float") is not None or string.strstr(fn_nm.id, "Double") is not None:
|
||||||
or string.strstr(fn_nm.id, "Float") is not None or string.strstr(fn_nm.id, "Double") is not None:
|
|
||||||
return "double"
|
return "double"
|
||||||
# char 内置类型(char(x) 或含 "Char" 的 t/c 类名:CChar/UChar 等)
|
# char 内置类型(char(x) 或含 "Char" 的 t/c 类名:CChar/UChar 等)
|
||||||
# → "char",与 float/double 并列(此前归 int 组丢失 i8 语义)
|
# → "char",与 float/double 并列(此前归 int 组丢失 i8 语义)
|
||||||
if string.strcmp(fn_nm.id, "char") == 0 \
|
if string.strcmp(fn_nm.id, "char") == 0 or string.strstr(fn_nm.id, "Char") is not None:
|
||||||
or string.strstr(fn_nm.id, "Char") is not None:
|
|
||||||
return "char"
|
return "char"
|
||||||
if string.strcmp(fn_nm.id, "int") == 0 \
|
if string.strcmp(fn_nm.id, "int") == 0 \
|
||||||
or string.strstr(fn_nm.id, "Int") is not None or string.strstr(fn_nm.id, "UInt") is not None \
|
or string.strstr(fn_nm.id, "Int") is not None or string.strstr(fn_nm.id, "UInt") is not None \
|
||||||
|
|||||||
@@ -262,6 +262,25 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
|||||||
if string.strstr(cls_name, "ClassDef") is not None:
|
if string.strstr(cls_name, "ClassDef") is not None:
|
||||||
if e is None or e.Ty is None:
|
if e is None or e.Ty is None:
|
||||||
stdio.printf("[DBG-P7] true_def MISS cls=%s cand=%s\n", cls_name, cand_sha1)
|
stdio.printf("[DBG-P7] true_def MISS cls=%s cand=%s\n", cls_name, cand_sha1)
|
||||||
|
# 结构体表里所有名字含 ClassDef 的条目(名/归属 sha1/IR 名前缀):
|
||||||
|
# 一眼看出"别名 entry 究竟有没有、挂在哪个 sha1 上"。
|
||||||
|
_sc_dbg: int = self.Gen.StructGen.StructCount
|
||||||
|
_si_dbg: int = 0
|
||||||
|
while _si_dbg < _sc_dbg:
|
||||||
|
_ee_dbg: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(_si_dbg)
|
||||||
|
if _ee_dbg is not None and _ee_dbg.Name is not None:
|
||||||
|
if string.strstr(_ee_dbg.Name, "ClassDef") is not None:
|
||||||
|
_ms_dbg: str = _ee_dbg.ModuleSha1
|
||||||
|
if _ms_dbg is None:
|
||||||
|
_ms_dbg = "(nil)"
|
||||||
|
_ir_dbg: str = "(no_ty)"
|
||||||
|
if _ee_dbg.Ty is not None:
|
||||||
|
_ir_dbg = llvmlite.get_struct_name(_ee_dbg.Ty)
|
||||||
|
if _ir_dbg is None:
|
||||||
|
_ir_dbg = "(noname)"
|
||||||
|
stdio.printf("[DBG-P7] tbl[%d] name=%s mod=%s ty=%s\n",
|
||||||
|
_si_dbg, _ee_dbg.Name, _ms_dbg, _ir_dbg)
|
||||||
|
_si_dbg += 1
|
||||||
else:
|
else:
|
||||||
_nm_dbg: str = llvmlite.get_struct_name(e.Ty)
|
_nm_dbg: str = llvmlite.get_struct_name(e.Ty)
|
||||||
if _nm_dbg is None:
|
if _nm_dbg is None:
|
||||||
@@ -2480,6 +2499,12 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
|||||||
string.strcat(mangled_buf, "[")
|
string.strcat(mangled_buf, "[")
|
||||||
string.strcat(mangled_buf, slice_name)
|
string.strcat(mangled_buf, slice_name)
|
||||||
string.strcat(mangled_buf, "]")
|
string.strcat(mangled_buf, "]")
|
||||||
|
# [DBG-P7] 临时诊断:AnnotClassName 通道算出的泛型票名(含 ClassDef 才打)
|
||||||
|
if string.strstr(mangled_buf, "ClassDef") is not None:
|
||||||
|
_fi_dbg: str = from_imports
|
||||||
|
if _fi_dbg is None:
|
||||||
|
_fi_dbg = "?"
|
||||||
|
stdio.printf("[DBG-P7] annot_mangled=%s from_imports=%s\n", mangled_buf, _fi_dbg)
|
||||||
return mangled_buf
|
return mangled_buf
|
||||||
|
|
||||||
return None
|
return None
|
||||||
@@ -2579,6 +2604,10 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase):
|
|||||||
if gen_atn is not None:
|
if gen_atn is not None:
|
||||||
string.strcat(gen_mangled, gen_atn)
|
string.strcat(gen_mangled, gen_atn)
|
||||||
string.strcat(gen_mangled, "]")
|
string.strcat(gen_mangled, "]")
|
||||||
|
# [DBG-P7] 临时诊断:注解/字段侧算出的泛型特化名(含 ClassDef 才打)——
|
||||||
|
# 与构造函数侧 _extract_type_args_from_slice 的实参串对比即知哪一侧偏了。
|
||||||
|
if string.strstr(gen_mangled, "ClassDef") is not None:
|
||||||
|
stdio.printf("[DBG-P7] gen_mangled=%s\n", gen_mangled)
|
||||||
# [REPL 注入期] 只做类型/签名层面的事,**绝不触发模板体翻译**(特化延后)。
|
# [REPL 注入期] 只做类型/签名层面的事,**绝不触发模板体翻译**(特化延后)。
|
||||||
#
|
#
|
||||||
# 现场:Repl 会话初始化先**真注册**了 includes 泛型模板(Repl.vp 步骤③''),
|
# 现场:Repl 会话初始化先**真注册**了 includes 泛型模板(Repl.vp 步骤③''),
|
||||||
|
|||||||
Reference in new issue
Block a user