fix(ExprGenBase): REnum 变体构造纳入「构造物化」豁免;错误报文只报问题本身

1) _ctor_call_agg_ty 增加 REnum 变体构造识别:`EnumType.Variant(...)` 的变体名不是结构体名
   (find_struct 必 miss)⇒ 取属性链最右裸名,若判为基础枚举则返回该枚举的聚合类型。
   否则 `*槽 = <boxed 变体>` 会被「指针 → 非基本类型值」禁令拦下——
   现场 ClassGenDef.vp:128 `*existing.Ty = llvmlite.LLVMType.Struct(...)`。
2) 按用户要求简化 Error 输出:CastError 只报问题(原因 + 源/目标类型),去掉
   ";出错表达式: <形态>(行/列)源码: ..." 后缀(文件与行号已由 TypeGenErr 的 Traceback
   帧给出,属重复展示),并删除随之失去调用点的 _cast_fail_where / _source_snippet。
This commit is contained in:
TermiNexus committed 2026-10-01 21:17:49 +08:00
1 parent a714ec9d37
commit 21d5cd1cd1
2 files changed
+29 -96

No files matched your search

+28 -95
View File
@@ -362,91 +362,8 @@ novirtual class ExprGenBase:
v = (ast.Return*(node)).value
if v is None:
return node
return v
# ============================================================
# _source_snippet - 尽力取「源码片段」(行文本),失败返回空串
#
# 报文风格对齐本仓既有「源码片段 + 行:列」(源行由 TypeGenErr.fatal_error 的
# Traceback 帧也会打印;此处再在报文内附一份,便于单行错误消息自洽可读)。
# 行文本由 stdlib.malloc 分配,拼进 f-string 后立即 free。
# ============================================================
def _source_snippet(self, lineno: int) -> str:
"""尽力读取第 lineno 行源码文本,拼成片段;取不到返回空串"""
if lineno <= 0:
return ""
file_name: str = self.Gen.TypeGenResolve.get_current_file()
if file_name is None:
return ""
line: str = self.Gen.TypeGenErr.read_source_line(file_name, lineno)
if line is None:
return ""
body: str = GenApi.escape_fstring_copy(f"\n 源码: {line}")
stdlib.free(line)
return body
# ============================================================
# _cast_fail_where - 隐式转换失败时报出**出错的表达式**(→ 定位到具体实参)
#
# 调用点只传 AST 节点,报错原本只有"调用所在行",多实参时看不出是哪一个
# (如 `_td_set_token(td, TD_TOKEN_IN, dev_addr, endpoint, toggle_ptr, max_pkt)`
# 报指针→整数,无法判断是哪个实参)。此处按节点形态给出可读名:
# Name→'id'、Attribute→'.attr'、Call/Subscript→形态名,并附**行:列**。
#
# 诊断错位修复(2026-09-28):
# ① 先经 _value_node_of 把外层语句节点(Assign kind=3 等)下沉到真正被转换的
# 值子表达式节点,再按形态描述;
# ② 列号 col_offset 缺省为 -1,此前按无符号格式化成 4294967295;现按有符号
# 打印,未知(<0)一律显示 '-',绝不出现 4294967295;
# ③ 尽力附源码片段(行 + 列 + 源行文本)。
# ============================================================
def _cast_fail_where(self, node: ast.AST*) -> str:
"""取隐式转换失败的定位文本(出错表达式 + 行:列 + 源码片段)
返回值会**逃逸本函数**(被 coerce_to_type 拼进更大的报文),而 f-string 结果
指向当前函数的栈缓冲 ⇒ 每处都经 GenApi.escape_fstring_copy 显式拷到 stdlib 堆
(历史事故:直接 return f"..." 让调用方读到已弹栈的帧,ASan
stack-buffer-underflow in vsnprintf)。
"""
node = self._value_node_of(node)
if node is None:
return "节点未知(无 AST 信息)"
k: int = node.kind()
pos: int = -1
where: str = None
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None:
pos = nm.col_offset
where = f"Name '{nm.id}'"
elif k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
if at.attr is not None:
pos = at.col_offset
where = f"属性 '.{at.attr}'"
elif k == ast.ASTKind.Call:
pos = (ast.Call*(node)).col_offset
where = "调用结果 Call"
elif k == ast.ASTKind.Subscript:
pos = (ast.Subscript*(node)).col_offset
where = "下标表达式 Subscript"
elif k == ast.ASTKind.Constant:
pos = (ast.Constant*(node)).col_offset
where = "常量 Constant"
if where is None:
where = f"表达式 kind={k}"
# 列号按**有符号**打印:col_offset 未知/缺省为 -1,若按无符号会打成
# 4294967295;未知一律显示 '-'。
col_txt: str = "-"
if pos >= 0:
col_txt = f"{pos}"
# 行号:node.lineno 缺省为 0/负,未知显示 '-'。
ln: int = node.lineno
ln_txt: str = "-"
if ln > 0:
ln_txt = f"{ln}"
snippet: str = self._source_snippet(ln)
return GenApi.escape_fstring_copy(f"{where}(行 {ln_txt},列 {col_txt}){snippet}")
# ============================================================
return v
# ============================================================
# IsNoneValue - 判据:源值是否为 None(空值常量),返回 1=是 / 0=否
#
# None 是特殊的空值常量:语义上「无值」,按语言规则**允许隐式转换为任意
@@ -780,9 +697,25 @@ novirtual class ExprGenBase:
if callee is None:
return None
ent: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(callee)
if ent is None or ent.Ty is None:
return None
return ent.Ty
if ent is not None and ent.Ty is not None:
return ent.Ty
# REnum 变体构造 `EnumType.Variant(...)`:被调方是「基础枚举.变体」,变体名不是
# 结构体名(find_struct 必 miss);但构造结果物化的就是**基础枚举**的整对象
# (boxed 指针指向该枚举结构体)⇒ 返回该 REnum 的聚合类型,使「构造物化」豁免
# 覆盖 `*槽 = <boxed 变体>`。否则该写法会被「指针 → 非基本类型值」禁令拦下
# (现场 ClassGenDef.vp:128 `*existing.Ty = llvmlite.LLVMType.Struct(...)`)。
if cl.func.kind() == ast.ASTKind.Attribute:
base_v: ast.AST* = (ast.Attribute*(cl.func)).value
# 取属性链最右的裸名(llvmlite.LLVMType → LLVMType)
while base_v is not None and base_v.kind() == ast.ASTKind.Attribute:
base_v = (ast.Attribute*(base_v)).value
if base_v is not None and base_v.kind() == ast.ASTKind.Name:
base_nm: str = (ast.Name*(base_v)).id
if base_nm is not None and self.Gen.ClassGenEnum._is_renum_class_name(base_nm) != 0:
rn_ent: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(base_nm)
if rn_ent is not None and rn_ent.Ty is not None:
return rn_ent.Ty
return None
# ============================================================
# _same_agg_ty - 两个聚合类型是否为「同一非基本类型」(构造物化豁免的类型匹配)
# ============================================================
@@ -851,7 +784,7 @@ novirtual class ExprGenBase:
return llvmlite.build_inttoptr(builder, llvmlite.const_int64(builder.Pool, 0), target_ty)
m_i2p: str = "整数隐式转指针已禁止:必须显式强转为指针(如 t.CPtr(x))才能把整数赋给指针类型"
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node),
f"{m_i2p};出错表达式: {self._cast_fail_where(node)}", "CastError")
m_i2p, "CastError")
return val
# Pointer -> integer: rejected (symmetric with the integer -> pointer rule above).
# No implicit pointer <-> integer conversion is allowed; an explicit cast is
@@ -860,7 +793,7 @@ novirtual class ExprGenBase:
if self.is_ptr_type(val.Ty) != 0 and target_bits != 0 and self.is_ptr_type(target_ty) == 0:
m_p2i: str = "指针隐式转整数已禁止:必须显式强转(如 uint64(p) / t.CInt64T(p) / int(p))才能把指针赋给整数类型"
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node),
f"{m_p2i};出错表达式: {self._cast_fail_where(node)}", "CastError")
m_p2i, "CastError")
return val
if val_bits != 0 and target_bits != 0:
if val_bits == target_bits:
@@ -906,7 +839,7 @@ novirtual class ExprGenBase:
if (val_fbits != 0 and self.is_ptr_type(target_ty) != 0) or \
(self.is_ptr_type(val.Ty) != 0 and target_fbits != 0):
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node),
f"unsupported type conversion: float <-> ptr;出错表达式: {self._cast_fail_where(node)}", "CastError")
"unsupported type conversion: float <-> ptr", "CastError")
return val
if val_fbits != 0 and target_fbits != 0:
if val_fbits == target_fbits:
@@ -932,7 +865,7 @@ novirtual class ExprGenBase:
v2p_txt: str = GenApi.escape_fstring_copy(
f";源值类型 {self._type_text(builder.Pool, val.Ty)} → 目标类型 {self._type_text(builder.Pool, target_ty)}")
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node),
f"{m_v2p}{v2p_txt};出错表达式: {self._cast_fail_where(node)}", "CastError")
f"{m_v2p}{v2p_txt}", "CastError")
return val
tmp_agg: llvmlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, val.Ty)
if tmp_agg is not None:
@@ -962,11 +895,11 @@ novirtual class ExprGenBase:
p2v_txt: str = GenApi.escape_fstring_copy(
f";源类型 {self._type_text(builder.Pool, val.Ty)} → 目标值类型 {self._type_text(builder.Pool, target_ty)}")
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node),
f"{m_p2v}{p2v_txt};出错表达式: {self._cast_fail_where(node)}", "CastError")
f"{m_p2v}{p2v_txt}", "CastError")
return val
return llvmlite.build_load(builder, target_ty, val)
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node),
f"指针隐式转非指针值已禁止:必须显式解引用(c.Deref(p) / *p)或显式强转(uint64(p));出错表达式: {self._cast_fail_where(node)}", "CastError")
f"指针隐式转非指针值已禁止:必须显式解引用(c.Deref(p) / *p)或显式强转(uint64(p))", "CastError")
return val
# 指针 → 指针: 隐式转换须满足两条新规则(见 _ptr_cast_reason 单一真源):
# 规则 1 通用指针(t.CPtr / i8*)与任何 T* 双向互转都放行;
@@ -979,7 +912,7 @@ novirtual class ExprGenBase:
if pc != PC_OK:
reason: str = self._ptr_cast_reason_text(pc, builder.Pool, val.Ty, target_ty)
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node),
f"{reason};出错表达式: {self._cast_fail_where(node)}", "CastError")
reason, "CastError")
return val
return llvmlite.build_bitcast(builder, val, target_ty)
return val
+1 -1
View File
@@ -166,7 +166,7 @@ novirtual class ExprGenFstr(ExprGenBase.ExprGenBase):
# ASan stack-buffer-underflow in vsnprintf(READ size 1,栈左红区)。
# · 确需逃逸时**显式分配后拷贝**:GenApi.escape_fstring_copy
# (stdlib.malloc + string.strcpy);编译器自身的报文构造即此写法
# (ExprGenBase._cast_fail_where / AssignGenMixin.const_assign_msg)。
# (AssignGenMixin.const_assign_msg)。
buf_ty: llvmlite.LLVMType* = llvmlite.Array(pool, i8_ty, FSTR_BUF_SIZE)
buf_alloca: llvmlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, buf_ty)
if buf_alloca is None: return None