feat(cast): 指针隐式转换按“同级+被指类型无损”判定(通用指针 t.CPtr/i8* 通配),并修诊断错位

_ptr_cast_reason 统一判据:层数不同→禁;完全相同类型→放行;任一侧通用指针(i8*/t.CPtr)→放行;否则按被指类型:非基本类型→禁,基本标量按既有无损规则(int↔int、float↔float、int→float 需目标浮点位宽≥源整数位宽、float→int 有损)

4 处禁令与新增的“指针→指针”处统一走 _value_node_of/_cast_fail_where:外层 Assign(kind=3) 下沉到真正被转换的值节点;列号按有符号打印(-1/未知打 -,不再出现 4294967295);附源码片段

未动“聚合值↔指针”那条路径(构造返回指针 load 回结构体,如 p: Point = Point()):LLVM 层无法与真实 Foo*→Foo 区分,禁止会直接编不过;规则③在该轴上的收紧待 AST 层区分构造调用后另做
This commit is contained in:
TermiNexus committed 2026-09-28 15:10:30 +08:00
1 parent 59a9fc5b1f
commit ee50ce61fe
1 file changed
+274 -14
+274 -14
View File
@@ -5,6 +5,7 @@ import ast
import llvmlite
import memhub
import string
import stdlib
import viperlib
import lib.core.Gen.Translator as HT
import lib.Projectrans.Utils as Utils
@@ -12,6 +13,22 @@ import lib.core.Gen.Type.TypeSpec as TSpec
import lib.core.Gen.GenApi as GenApi
# ============================================================
# 指针间隐式转换判定码(coerce_to_type 的「指针 → 指针」分支使用)
#
# 由 _ptr_cast_reason 统一给出,供报错说明「哪一级不允许 + 为什么」:
# PC_OK 允许(含规则 1 通用指针通配、规则 4 完全相同不视为转换)
# PC_DEPTH 指针层数不同(禁)
# PC_NONBASIC 被指类型是非基本类型(结构体/类/联合/数组等):非基本类型与其
# 指针之间、以及不同非基本类型的指针之间一律不允许(规则 2/3)
# PC_LOSSY 被指类型之间转换有损(整数→浮点位宽不足 / 浮点→整数)(规则 2)
# ============================================================
define PC_OK = 0
define PC_DEPTH = 1
define PC_NONBASIC = 2
define PC_LOSSY = 3
# ============================================================
# ExprGenBase - ExprGenMixin 拆分(阶段 D):表达式基础工具(类型判断/指针层级/coerce)
# ============================================================
@@ -318,46 +335,115 @@ novirtual class ExprGenBase:
if loaded is None: return obj_ptr
return loaded
# ============================================================
# _value_node_of - 把「外层语句节点」下沉到其「值子表达式节点」
#
# 诊断错位修复(2026-09-28):赋值/返回等路径传给 coerce_to_type 的 node 常是
# 外层语句节点(实测 Assign,kind=3,col_offset=-1),导致报错打成
# `出错表达式: 表达式 kind=3(列 -1)`,指不到真正出错的子表达式。
# 此处把语句节点(Assign/AnnAssign/AugAssign/Expr/Return)换成其 value 子节点,
# 使报错定位到真正被转换的那个值表达式。非语句节点原样返回。
# ============================================================
def _value_node_of(self, node: ast.AST*) -> ast.AST*:
"""语句节点 → 其值子表达式节点;非语句/无值则原样返回"""
if node is None:
return node
k: int = node.kind()
v: ast.AST* = None
if k == ast.ASTKind.Assign:
v = (ast.Assign*(node)).value
elif k == ast.ASTKind.AnnAssign:
v = (ast.AnnAssign*(node)).value
elif k == ast.ASTKind.AugAssign:
v = (ast.AugAssign*(node)).value
elif k == ast.ASTKind.Expr:
v = (ast.Expr*(node)).value
elif k == ast.ASTKind.Return:
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→形态名,并附列号(源码行已
# 由 traceback 打出,列号即可指到具体实参)。
# 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
return GenApi.escape_fstring_copy(f"Name '{nm.id}'(列 {pos})")
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
return GenApi.escape_fstring_copy(f"属性 '.{at.attr}'(列 {pos})")
where = f"属性 '.{at.attr}'"
elif k == ast.ASTKind.Call:
pos = (ast.Call*(node)).col_offset
return GenApi.escape_fstring_copy(f"调用结果 Call(列 {pos})")
where = "调用结果 Call"
elif k == ast.ASTKind.Subscript:
pos = (ast.Subscript*(node)).col_offset
return GenApi.escape_fstring_copy(f"下标表达式 Subscript(列 {pos})")
where = "下标表达式 Subscript"
elif k == ast.ASTKind.Constant:
pos = (ast.Constant*(node)).col_offset
return GenApi.escape_fstring_copy(f"常量 Constant(列 {pos})")
return GenApi.escape_fstring_copy(f"表达式 kind={k}(列 {pos})")
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}")
# ============================================================
# IsNoneValue - 判据:源值是否为 None(空值常量),返回 1=是 / 0=否
@@ -421,6 +507,165 @@ novirtual class ExprGenBase:
return llvmlite.ConstFloat(pool, target_ty, 0.0)
return llvmlite.ConstZero(pool, target_ty)
# ============================================================
# _is_universal_ptr_ty - 判据:类型是否为「通用指针」(t.CPtr / i8* = void*)
#
# 规则 1(通用指针是通配):t.CPtr 与 i8*(void*,裸 str/bytes 那一族的指针形态)
# 视为「任意指针」,与任何 T* 双向隐式互转都放行。
# 判据:单层 Ptr 且 pointee 为 i8(t.CPtr = Ptr(Int8);Ptr(Void) 工厂已回退为
# Ptr(Int8),故 void* 在 LLVM 层同样表现为 Ptr(i8))。char* 同属 i8*,一并视为通用指针。
# 多级指针(如 i8**)不是通用指针(需逐级比较 pointee)。
# 返回 1=是 / 0=否。
# ============================================================
def _is_universal_ptr_ty(self, ty: llvmlite.LLVMType*) -> int:
"""ty 是否为通用指针(i8*/t.CPtr),1=是 / 0=否"""
if ty is None or self.is_ptr_type(ty) == 0:
return 0
if self.get_ptr_level(ty) != 1:
return 0
pointee: llvmlite.LLVMType* = ty.Pointee
if pointee is None:
return 1
if self.get_llvm_type_bits(pointee) == 8:
return 1
return 0
# ============================================================
# _is_basic_scalar_ty - 判据:类型是否为基本类型(整数/浮点),1=是 / 0=否
#
# 非基本类型 = 结构体/类/联合/数组/函数等(LLVM 层 Struct/Array/Func)。
# 规则 2/3:非基本类型一律不允许参与指针间隐式转换(除完全相同类型,见规则 4)。
# ============================================================
def _is_basic_scalar_ty(self, ty: llvmlite.LLVMType*) -> int:
"""ty 是否为基本类型(整数/浮点),1=是 / 0=否"""
if ty is None:
return 0
if self.get_llvm_type_bits(ty) != 0:
return 1
if self.get_llvm_float_bits(ty) != 0:
return 1
return 0
# ============================================================
# _scalar_convert_reason - 基本标量 T → U 是否无损(沿用 coerce_to_type 既有规则)
#
# 整数/浮点:沿用既有规则(高→低允许安全降级、低→高无损;整数→浮点需浮点位宽
# ≥ 整数位宽;浮点→整数不允许)。
# 返回 PC_OK(无损/允许)或 PC_LOSSY(有损/禁止)。
# ============================================================
def _scalar_convert_reason(self, src_pt: llvmlite.LLVMType*,
dst_pt: llvmlite.LLVMType*) -> int:
"""基本标量 src_pt → dst_pt 的隐式转换判定码(PC_OK / PC_LOSSY)"""
s_int: int = self.get_llvm_type_bits(src_pt)
d_int: int = self.get_llvm_type_bits(dst_pt)
s_flt: int = self.get_llvm_float_bits(src_pt)
d_flt: int = self.get_llvm_float_bits(dst_pt)
# 整数 ↔ 整数:高→低安全降级、低→高无损,均允许
if s_int != 0 and d_int != 0:
return PC_OK
# 浮点 ↔ 浮点:允许
if s_flt != 0 and d_flt != 0:
return PC_OK
# 整数 → 浮点:浮点位宽 ≥ 整数位宽才无损
if s_int != 0 and d_flt != 0:
if d_flt >= s_int:
return PC_OK
return PC_LOSSY
# 浮点 → 整数:有损,禁止
if s_flt != 0 and d_int != 0:
return PC_LOSSY
return PC_OK
# ============================================================
# _pointee_convert_reason - 被指类型 src_pt → dst_pt(T*→U* 的 T→U 关系)判定码
#
# 规则 2:T* → U*(T、U 都非通用指针)允许 ⇔ 被指类型 T → U 无损。
# 规则 3:非基本类型 T 与其指针 T* 之间(值/指针互换)一律禁止——此处体现为
# 被指类型一侧是「非基本值类型」、另一侧是「其指针(或其它的基本/非基本类型)」
# 时一律不匹配 ⇒ PC_NONBASIC。
# 被指类型仍是指针(T** → U**):递归按指针规则判。
# ============================================================
def _pointee_convert_reason(self, pool: memhub.MemBuddy*,
src_pt: llvmlite.LLVMType*,
dst_pt: llvmlite.LLVMType*) -> int:
"""被指类型 src_pt → dst_pt 的判定码(PC_OK/PC_NONBASIC/PC_LOSSY/PC_DEPTH)"""
if src_pt is None or dst_pt is None:
return PC_OK
s_is_ptr: int = self.is_ptr_type(src_pt)
d_is_ptr: int = self.is_ptr_type(dst_pt)
if s_is_ptr != 0 or d_is_ptr != 0:
# 一侧是指针、另一侧不是(如被指类型 Foo vs Foo*)→ 非基本类型值/指针互换,禁止
if s_is_ptr == 0 or d_is_ptr == 0:
return PC_NONBASIC
# 两侧都是指针:递归
return self._ptr_cast_reason(pool, src_pt, dst_pt)
# 一侧是非基本类型(结构体/类/联合/数组/函数)→ 一律禁止
if self._is_basic_scalar_ty(src_pt) == 0 or self._is_basic_scalar_ty(dst_pt) == 0:
return PC_NONBASIC
return self._scalar_convert_reason(src_pt, dst_pt)
# ============================================================
# _ptr_cast_reason - 指针 → 指针隐式转换的判定码(新规则单一真源)
#
# 规则 1:通用指针(t.CPtr / i8*)与任何 T* 双向互转都放行 ⇒ PC_OK。
# 规则 2:T* → U*(T、U 都非通用指针)允许 ⇔ 被指类型 T → U 无损;非基本类型
# 一律不允许。
# 规则 3:非基本类型 T 与其指针 T* 之间一律禁止。
# 规则 4:完全相同类型(指针深度与被指类型都相同,含 Foo* → Foo*)不算转换 ⇒ PC_OK。
# 返回 PC_OK(允许)/ PC_DEPTH / PC_NONBASIC / PC_LOSSY。
# ============================================================
def _ptr_cast_reason(self, pool: memhub.MemBuddy*,
src_ty: llvmlite.LLVMType*,
dst_ty: llvmlite.LLVMType*) -> int:
"""指针 → 指针对隐式转换的判定码(PC_OK 表示允许)"""
if src_ty is None or dst_ty is None:
return PC_OK
slev: int = self.get_ptr_level(src_ty)
dlev: int = self.get_ptr_level(dst_ty)
if slev == 0 or dlev == 0:
return PC_OK
# 层数不同 → 禁止(无论被指类型)
if slev != dlev:
return PC_DEPTH
# 规则 4:完全相同类型(层数 + 逐级被指类型都相同)不算转换
if self.Gen.AssignGen._stg_ty_equal(pool, src_ty, dst_ty) != 0:
return PC_OK
# 规则 1:通用指针是通配(i8* ↔ 任何 T*)
if self._is_universal_ptr_ty(src_ty) != 0 or self._is_universal_ptr_ty(dst_ty) != 0:
return PC_OK
# 规则 2/3:逐级比较被指类型
return self._pointee_convert_reason(pool, src_ty.Pointee, dst_ty.Pointee)
# ============================================================
# _type_text - LLVM 类型的可读文本(TypePrint),供报文使用(失败返回 "?")
# ============================================================
def _type_text(self, pool: memhub.MemBuddy*,
ty: llvmlite.LLVMType*) -> str:
"""LLVM 类型文本(TypePrint);失败返回 "?" """
if ty is None:
return "?"
buf: str = pool.alloc(256)
if buf is None:
return "?"
llvmlite.TypePrint(buf, 256, ty, pool)
if buf[0] == 0:
return "?"
return buf
# ============================================================
# _ptr_cast_reason_text - 把指针间隐式转换的禁止原因写成可读文本
#
# 报文写清「哪一级不允许 + 为什么(非基本类型 / 有损 / 层数不同)」,并给出
# 源/目标 LLVM 类型文本。返回值会逃逸 ⇒ 经 escape_fstring_copy 拷到 stdlib 堆。
# ============================================================
def _ptr_cast_reason_text(self, code: int, pool: memhub.MemBuddy*,
src_ty: llvmlite.LLVMType*,
dst_ty: llvmlite.LLVMType*) -> str:
"""指针间隐式转换禁止原因的可读文本(含源/目标类型)"""
why: str = "原因未知"
if code == PC_DEPTH:
why = f"指针层数不同(源 {self.get_ptr_level(src_ty)} 级 → 目标 {self.get_ptr_level(dst_ty)} 级)"
elif code == PC_NONBASIC:
why = "被指类型是非基本类型(结构体/类/联合/数组等):非基本类型与其指针之间、以及不同非基本类型的指针之间一律不允许隐式转换"
elif code == PC_LOSSY:
why = "被指类型之间的转换有损(整数→浮点位宽不足 / 浮点→整数)"
s_txt: str = self._type_text(pool, src_ty)
d_txt: str = self._type_text(pool, dst_ty)
return GenApi.escape_fstring_copy(f"指针隐式转指针已禁止:{why};源类型 {s_txt} → 目标类型 {d_txt}")
# ============================================================
# 类型强制转换:整数 sext/trunc,浮点 fpext/fptrunc,int↔float si2fp/fp2si
# ============================================================
def coerce_to_type(self, builder: llvmlite.IRBuilder*,
@@ -478,7 +723,8 @@ novirtual class ExprGenBase:
if val.IsConst == 1 and val.IntVal == 0:
return llvmlite.build_inttoptr(builder, llvmlite.const_int64(builder.Pool, 0), target_ty)
m_i2p: str = "整数隐式转指针已禁止:必须显式强转为指针(如 t.CPtr(x))才能把整数赋给指针类型"
self.Gen.TypeGenErr.fatal_error(node, f"{m_i2p};出错表达式: {self._cast_fail_where(node)}", "CastError")
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node),
f"{m_i2p};出错表达式: {self._cast_fail_where(node)}", "CastError")
return val
# Pointer -> integer: rejected (symmetric with the integer -> pointer rule above).
# No implicit pointer <-> integer conversion is allowed; an explicit cast is
@@ -486,7 +732,8 @@ novirtual class ExprGenBase:
# through to the build_load branch below, copying the pointee's first byte.)
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(node, f"{m_p2i};出错表达式: {self._cast_fail_where(node)}", "CastError")
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node),
f"{m_p2i};出错表达式: {self._cast_fail_where(node)}", "CastError")
return val
if val_bits != 0 and target_bits != 0:
if val_bits == target_bits:
@@ -520,7 +767,8 @@ novirtual class ExprGenBase:
# float ↔ ptr: 禁止(无意义的转换,fast-fail 防止误走入 build_load 路径)
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(node, "unsupported type conversion: float <-> ptr", "CastError")
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node),
f"unsupported type conversion: float <-> ptr;出错表达式: {self._cast_fail_where(node)}", "CastError")
return val
if val_fbits != 0 and target_fbits != 0:
if val_fbits == target_fbits:
@@ -558,10 +806,22 @@ novirtual class ExprGenBase:
target_kind: int = target_ty.kind()
if target_kind == llvmlite.LLVMType.Struct or target_kind == llvmlite.LLVMType.Array:
return llvmlite.build_load(builder, target_ty, val)
self.Gen.TypeGenErr.fatal_error(node, "指针隐式转非指针值已禁止:必须显式解引用(c.Deref(p) / *p)或显式强转(uint64(p))", "CastError")
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node),
f"指针隐式转非指针值已禁止:必须显式解引用(c.Deref(p) / *p)或显式强转(uint64(p));出错表达式: {self._cast_fail_where(node)}", "CastError")
return val
# 指针 → 指针: bitcast (如 i8* → i8** 当目标是全局变量存储指针)
# 指针 → 指针: 隐式转换须满足两条新规则(见 _ptr_cast_reason 单一真源):
# 规则 1 通用指针(t.CPtr / i8*)与任何 T* 双向互转都放行;
# 规则 2 T* → U* 允许 ⇔ 被指类型 T → U 无损,非基本类型一律禁止;
# 规则 3 非基本类型 T 与其指针 T* 之间一律禁止;
# 规则 4 完全相同类型(含 Foo* → Foo*)不算转换,照常通过。
# 违反者沿用既有 fail-fast 风格报错(CastError + 「…已禁止:」前缀)。
if self.is_ptr_type(val.Ty) != 0 and self.is_ptr_type(target_ty) != 0:
pc: int = self._ptr_cast_reason(builder.Pool, val.Ty, target_ty)
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")
return val
return llvmlite.build_bitcast(builder, val, target_ty)
return val