diff --git a/App/lib/core/Gen/Expr/ExprGenBase.vp b/App/lib/core/Gen/Expr/ExprGenBase.vp index df3a842..995b625 100644 --- a/App/lib/core/Gen/Expr/ExprGenBase.vp +++ b/App/lib/core/Gen/Expr/ExprGenBase.vp @@ -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