diff --git a/Projectrans.py b/Projectrans.py index 9b8b811..e09283e 100644 --- a/Projectrans.py +++ b/Projectrans.py @@ -32,6 +32,7 @@ if __name__ == '__main__': import sys as _sys import time as _time from lib.Projectrans.Phase2Translator import GetProfRunDir as _GetProfRunDir + from lib.Projectrans.Phase2Translator import ReportedCompileError as _ReportedCompileError _main_prof: _cProfile.Profile = _cProfile.Profile() _main_prof.enable() @@ -39,6 +40,11 @@ if __name__ == '__main__': try: main() _timing_status: str = '成功' + except _ReportedCompileError: + # 完整错误(含源码定位)已由主进程打印过一次:这里只置失败并退出, + # 不再重复输出同一错误。 + _timing_status = '失败' + raise SystemExit(1) except (TypeError, SyntaxError) as _e: _timing_status = '失败' import re as _re diff --git a/lib/Projectrans/Phase2Translator.py b/lib/Projectrans/Phase2Translator.py index d464d24..91b2255 100644 --- a/lib/Projectrans/Phase2Translator.py +++ b/lib/Projectrans/Phase2Translator.py @@ -27,6 +27,15 @@ from StubGen import PythonToStubConverter from lib.core.LLVMCG.IRMetadataFix import SanitizeIRMetadata +class ReportedCompileError(RuntimeError): + """主进程已输出完整编译错误(含源码定位)的终止信号。 + + 该异常只用于"错误已经打印过"的路径:main()/run() 抛出后, + 顶层入口(Projectrans.py)不得再次打印错误内容,直接以失败退出码结束, + 否则同一错误会在 worker/主进程/顶层各出现一遍。 + """ + + # PROF 热点:nt._path_isfile 2.6 万次 16.4s(Windows 磁盘 stat ~0.6ms/次)。 # 编译进程内文件状态稳定(obj/ll 一旦生成不删除、不被移动),用模块级缓存 # 避免重复 stat。仅用于"存在性检查"类调用(不用于内容变化的判断)。 @@ -763,11 +772,9 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp trans._translate_file(src_path, out_path, prebuilt_ModuleSha1Map=prebuilt_ModuleSha1Map, dwarf_enabled=dwarf_enabled) return ('ok', src_path, '') except Exception as e: - # fail-fast:只输出单行错误(含源码定位),不打印 Python 内部调用栈 - sys.stderr.write( - f"[FATAL] 翻译失败: {src_path}\n{type(e).__name__}: {e}\n") - sys.stderr.flush() - os._exit(88) + # fail-fast:worker 不写 stderr,只把错误(含源码定位)返回主进程; + # 由主进程统一输出一次,避免同一错误打印两遍。 + return ('error', src_path, f'{type(e).__name__}: {e}') def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_dir: str, @@ -787,7 +794,10 @@ def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_ 批内每个文件翻译前从 symbol_table_bytes 反序列化出干净副本,前文件的 符号写入不会残留到后文件(否则跨模块 struct 类型前缀错位)。 返回 (错误列表[(src_path, err)], batch, per_file_times[(src_path, elapsed, rel)])。 + 错误只回传、不写 stderr:完整错误由主进程统一输出一次(含源码定位)。 """ + _current_src: str = batch[0][1] if batch else '' + per_file_times: list[tuple[str, float, str]] = [] try: trans = Phase2Translator(src_root, temp_dir, output_dir, compile_cmd='llc', include_dirs=include_dirs, @@ -830,28 +840,21 @@ def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_ trans._build_shared_symbol_data() batch_errors: list = [] - per_file_times: list[tuple[str, float, str]] = [] for _i, src_path, out_path, _sha1, msm in batch: _f0: float = time.time() - try: - # 每文件前重置 SymbolTable 为干净副本(防跨文件污染) - if symbol_table_bytes is not None: - trans._shared_symbol_table = pickle.loads(symbol_table_bytes) - trans._shared_symbol_table.translator = None - trans._translate_file(src_path, out_path, prebuilt_ModuleSha1Map=msm, dwarf_enabled=dwarf_enabled) - per_file_times.append((src_path, time.time() - _f0, os.path.relpath(src_path, src_root))) - except Exception as e: - # fail-fast(2026-08-18):任意文件编译出错立即终止整个编译进程, - # 不再收集错误继续翻译批内剩余文件(避免浪费大量时间)。主进程 - # 收到 BrokenProcessPool/future 异常后取消全部剩余批次退出。 - sys.stderr.write( - f"[FATAL] 文件翻译失败: {src_path}\n" - f"{type(e).__name__}: {e}\n") - sys.stderr.flush() - os._exit(88) + _current_src = src_path + # 每文件前重置 SymbolTable 为干净副本(防跨文件污染) + if symbol_table_bytes is not None: + trans._shared_symbol_table = pickle.loads(symbol_table_bytes) + trans._shared_symbol_table.translator = None + trans._translate_file(src_path, out_path, prebuilt_ModuleSha1Map=msm, dwarf_enabled=dwarf_enabled) + per_file_times.append((src_path, time.time() - _f0, os.path.relpath(src_path, src_root))) return (batch_errors, batch, per_file_times) except Exception as e: - return ([(item[1], f'{type(e).__name__}: {e}') for item in batch], batch, []) + # fail-fast:任意文件编译出错立即终止本批(不再翻译批内剩余文件)。 + # worker 不写 stderr —— 错误(含源码定位)随返回值交给主进程统一输出一次。 + _src: str = _current_src or (batch[0][1] if batch else '') + return ([(_src, f'{type(e).__name__}: {e}')], batch, per_file_times) # ================================================================== @@ -1709,7 +1712,6 @@ class Phase2Translator: _vlog().warning(f"符号表序列化失败({e}),worker将自行构建") shared_pickle_path = '' - errors = [] # 复用 self._executor_pool:避免局部变量 GC 时 finalizer 阻塞 ~99s # (join_executor_internals 在 GC 时同步等待 worker 退出队列) if self._executor_pool is None: @@ -1770,17 +1772,11 @@ class Phase2Translator: bidx, rel_first, bcount = futures[future] try: batch_errors, batch, per_file_times = future.result() - for src, elapsed, rel in per_file_times: - done_files += 1 - _vlog().info(f" [{done_files}/{total_files}] {rel} ({elapsed:.1f}s)") - for src, err in batch_errors: - rel = os.path.relpath(src, self.src_root) - _vlog().error(f" {rel}: {err}") - errors.append((rel, err)) - _vlog().info(f" [批次完成] {bcount} 文件, 首个 {rel_first}") + except ReportedCompileError: + raise except Exception as e: - # fail-fast:worker 已 os._exit(文件编译出错)或进程池 - # 已 broken → 立即取消全部剩余批次并终止编译,不空等。 + # fail-fast:进程池已 broken(worker 异常退出)→ 取消 + # 全部剩余批次并终止编译,不空等。 _vlog().error(f" 批次 {bidx} 异常(worker 已终止): {e}") for fut in futures: if not fut.done(): @@ -1788,8 +1784,26 @@ class Phase2Translator: if self._executor_pool is not None: self._executor_pool.shutdown(wait=False, cancel_futures=True) self._executor_pool = None - raise RuntimeError( - f"批次 {bidx} worker 异常终止,编译立即停止: {e}") + raise ReportedCompileError( + f"批次 {bidx} worker 异常终止,编译立即停止") from None + if batch_errors: + # worker 只回传错误、不写 stderr:完整错误(含源码定位) + # 只在这里输出一次,随后立即终止(fail-fast)。 + src, err = batch_errors[0] + rel = os.path.relpath(src, self.src_root) if src else rel_first + _vlog().error(f"\n文件翻译失败: {rel}\n{err}") + for fut in futures: + if not fut.done(): + fut.cancel() + if self._executor_pool is not None: + self._executor_pool.shutdown(wait=False, cancel_futures=True) + self._executor_pool = None + raise ReportedCompileError( + f"{rel} 翻译失败,编译立即停止") from None + for src, elapsed, rel in per_file_times: + done_files += 1 + _vlog().info(f" [{done_files}/{total_files}] {rel} ({elapsed:.1f}s)") + _vlog().info(f" [批次完成] {bcount} 文件, 首个 {rel_first}") except Exception: # 异常时立即清理,正常流程不 shutdown(留给 includes 阶段复用) if self._executor_pool is not None: @@ -1797,11 +1811,6 @@ class Phase2Translator: self._executor_pool = None raise - if errors: - _vlog().error(f"\n{len(errors)} 个文件翻译失败,立即终止编译") - for rel, err in errors: - _vlog().error(f" {rel}: {err}") - raise RuntimeError(f"{len(errors)} 个文件翻译失败,已终止编译") else: for idx, (i, src_path, out_path, sha1, msm) in enumerate(need_translate): rel = os.path.relpath(src_path, self.src_root) @@ -1811,9 +1820,10 @@ class Phase2Translator: t1 = time.time() _vlog().info(f" [{idx+1}/{len(need_translate)}] {rel} ({t1-t0:.1f}s)") except Exception as e: - _vlog().error(f" {rel}: {e}") - traceback.print_exc() - raise RuntimeError(f"翻译 {rel} 失败,已终止编译: {e}") from e + # 单点输出:完整错误(含源码定位)只打印这一次 + _vlog().error(f"\n文件翻译失败: {rel}\n{type(e).__name__}: {e}") + raise ReportedCompileError( + f"翻译 {rel} 失败,已终止编译") from None t_end = time.time() _vlog().info(f" 翻译总耗时: {t_end-t_start:.1f}s") @@ -3841,13 +3851,8 @@ class Phase2Translator: done_count += 1 try: status, _, err = future.result() - if status == 'ok': - _vlog().info(f" [{done_count}/{len(need_translate)}] 翻译完成: {module_name}.py") - else: - # fail-fast:文件编译出错立即终止(worker 已 os._exit, - # 此处兜底处理残留的 error 返回) - raise RuntimeError( - f"include 翻译失败,立即终止: {module_name}.py: {err}") + except ReportedCompileError: + raise except Exception as e: _vlog().error(f" {module_name}.py: {e}") for f2 in in_progress: @@ -3855,8 +3860,21 @@ class Phase2Translator: if self._executor_pool is not None: self._executor_pool.shutdown(wait=False, cancel_futures=True) self._executor_pool = None - raise RuntimeError( - f"include worker 异常终止,编译立即停止: {e}") + raise ReportedCompileError( + "include worker 异常终止,编译立即停止") from None + if status == 'ok': + _vlog().info(f" [{done_count}/{len(need_translate)}] 翻译完成: {module_name}.py") + else: + # worker 只回传错误、不写 stderr:完整错误(含源码定位) + # 只在这里输出一次,随后立即终止(fail-fast)。 + _vlog().error(f"\n文件翻译失败: {module_name}.py\n{err}") + for f2 in in_progress: + f2.cancel() + if self._executor_pool is not None: + self._executor_pool.shutdown(wait=False, cancel_futures=True) + self._executor_pool = None + raise ReportedCompileError( + "include 翻译失败,编译立即停止") from None except Exception: if self._executor_pool is not None: self._executor_pool.shutdown(wait=False, cancel_futures=True) @@ -3871,12 +3889,24 @@ class Phase2Translator: for item in need_translate: src_path, sha1, module_name, ll_path, obj_path, precompiled_obj = item _vlog().info(f" 翻译: {module_name}.py -> {sha1}.ll") - self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.traceback_enabled) + try: + self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.traceback_enabled) + except Exception as e: + # 单点输出:完整错误(含源码定位)只打印这一次 + _vlog().error(f"\n文件翻译失败: {module_name}.py\n{type(e).__name__}: {e}") + raise ReportedCompileError( + "include 翻译失败,编译立即停止") from None else: item = need_translate[0] src_path, sha1, module_name, ll_path, obj_path, precompiled_obj = item _vlog().info(f" 翻译: {module_name}.py -> {sha1}.ll") - self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.traceback_enabled) + try: + self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.traceback_enabled) + except Exception as e: + # 单点输出:完整错误(含源码定位)只打印这一次 + _vlog().error(f"\n文件翻译失败: {module_name}.py\n{type(e).__name__}: {e}") + raise ReportedCompileError( + "include 翻译失败,编译立即停止") from None # ========== 阶段2:串行后处理(dso_local + stub_decls 注入)========== compile_tasks = [] # [(dso_ll_path, obj_path, src_path, sha1, module_name, ll_path, precompiled_obj, ll_content)] diff --git a/lib/core/Handles/HandlesClassDef.py b/lib/core/Handles/HandlesClassDef.py index 2e7eafb..24083be 100644 --- a/lib/core/Handles/HandlesClassDef.py +++ b/lib/core/Handles/HandlesClassDef.py @@ -235,6 +235,110 @@ class ClassHandle(BaseHandle): tn_bare_cls: str = tn.split('.')[-1] if '.' in tn else tn if self._HasClassDefinition(tn_bare_cls, Gen): class_type_names.add(tn_bare_cls) + # 特化 AST:复制模板 → 清空 type_params → 改名为特化名,再整体替换 + # T(注解 + 方法体)。之后走与普通类完全相同的 _EmitClassLlvm 发射 + # (declare_only=False 时逐方法 _EmitFunctionLlvm 生成方法体)。 + SpecNode: ast.ClassDef = copy.deepcopy(Node) + SpecNode.type_params = [] + SpecNode.name = spec_name + for item in SpecNode.body: + if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name): + if item.annotation: + item.annotation = self.Trans.FunctionHandler._replace_type_in_annotation(item.annotation, type_map, cptr_params) + elif isinstance(item, ast.FunctionDef): + for arg in item.args.args: + if arg.annotation: + arg.annotation = self.Trans.FunctionHandler._replace_type_in_annotation(arg.annotation, type_map, cptr_params) + # class 类型参数加指针语义。T 替换后的真实形态可能是 + # Attribute 链('.GNode' → Name('').GNode, + # 见 _make_replacement_node/_make_name_node),不是 ast.Name, + # 故按裸名判据(_BareClassAnnotationName)判定;已经是指针 + # 下标(CPtr[T] / rawptr[T])的注解返回 None,不重复加层。 + raw_name: str | None = self._BareClassAnnotationName(arg.annotation) + if raw_name is not None and raw_name in class_type_names: + arg.annotation = ast.Subscript( + value=ast.Name(id='CPtr', ctx=ast.Load()), + slice=arg.annotation, + ctx=ast.Load(), + ) + if item.returns: + item.returns = self.Trans.FunctionHandler._replace_type_in_annotation(item.returns, type_map, cptr_params) + # 返回类型也加指针语义(同一裸名判据,形态同上) + raw_name: str | None = self._BareClassAnnotationName(item.returns) + if raw_name is not None and raw_name in class_type_names: + item.returns = ast.Subscript( + value=ast.Name(id='CPtr', ctx=ast.Load()), + slice=item.returns, + ctx=ast.Load(), + ) + self.Trans.FunctionHandler._apply_type_map_to_body(item.body, type_map, cptr_params) + saved_builder: ir.IRBuilder = Gen.builder + saved_func: ir.Function = Gen.func + saved_variables: dict = dict(Gen.variables) if Gen.variables else {} + saved_direct_values: dict = dict(Gen._direct_values) if Gen._direct_values else {} + saved_var_type_info: dict = dict(Gen.var_type_info) if Gen.var_type_info else {} + saved_var_signedness: dict = dict(Gen.var_signedness) if Gen.var_signedness else {} + saved_global_vars: set = set(Gen.global_vars) if Gen.global_vars else set() + saved_var_scopes: list = [dict(s) for s in self.Trans.VarScopes] if self.Trans.VarScopes else [] + saved_block: ir.Block | None = None + if Gen.builder and Gen.builder.block and not Gen.builder.block.is_terminated: + saved_block = Gen.builder.block + # 切换 module_sha1 为泛型模板定义模块的 SHA1, + # 使结构体类型名和方法定义名都用定义模块的 SHA1 前缀, + # 与调用端 _struct_sha1_map 查到的 SHA1 一致。 + template_sha1: str | None = template.get('sha1') + if not template_sha1: + # 模板注册表缺 sha1 时回退到"模板类定义模块"(_struct_sha1_map 即 + # 类名 → 定义模块表,Phase2 从各 .vpi 扫描构建):与声明端 + # DeclarationGenerator._get_type_str 的泛型前缀、调用点 + # Gen._struct_sha1_map[spec_name] 同一条规则源;否则 declare/define + # 两侧前缀分裂(使用方模块 vs 模板模块)→ 跨模块 undefined symbol。 + template_sha1 = Gen._struct_sha1_map.get(ClassName) + saved_gen_sha1: str | None = getattr(Gen, 'module_sha1', None) + saved_trans_sha1: str | None = getattr(self.Trans, '_module_sha1', None) + if template_sha1: + Gen.module_sha1 = template_sha1 + self.Trans._module_sha1 = template_sha1 + try: + self._EmitClassLlvm(SpecNode, Gen, declare_only=declare_only) + except BaseException: + # 失败时撤销预注册缓存:否则后续调用会把「特化到一半」当成已完成 + # (declare_only=False),方法体永远不会再补发,错误被伪装成 + # "没有 __len__ 方法 / Undefined method"。异常本身不吞,继续上抛。 + Gen._generic_class_specializations.pop(spec_key, None) + raise + finally: + # 确保 Gen 状态恢复,即使 _EmitClassLlvm 抛出异常(异常本身不吞, + # 由调用方记录到 Gen._generic_spec_error 或直接上抛)。 + Gen.builder = saved_builder + if saved_block is not None and Gen.builder is not None: + Gen.builder.position_at_end(saved_block) + Gen.func = saved_func + Gen.variables = saved_variables + Gen._direct_values = saved_direct_values + Gen.var_type_info = saved_var_type_info + Gen.var_signedness = saved_var_signedness + Gen.global_vars = saved_global_vars + self.Trans.VarScopes = saved_var_scopes + # 恢复原始 module_sha1 + Gen.module_sha1 = saved_gen_sha1 + self.Trans._module_sha1 = saved_trans_sha1 + Gen._generic_class_specializations[spec_key] = (spec_name, declare_only) + # 缓存 type_args/type_names,供后续 declare_only=False 完整特化补发方法体使用 + # (如 _HandleMethodCallLlvm 中发现方法未定义时触发) + if not hasattr(Gen, '_generic_class_spec_type_info'): + Gen._generic_class_spec_type_info = {} + Gen._generic_class_spec_type_info[spec_key] = (list(type_args), list(type_names) if type_names else None) + if not hasattr(self.Trans, '_generic_class_specializations'): + self.Trans._generic_class_specializations = {} + self.Trans._generic_class_specializations[spec_key] = spec_name + # 用模板定义模块的 SHA1 注册到 _struct_sha1_map,与方法定义端一致 + if template_sha1: + Gen._struct_sha1_map[spec_name] = template_sha1 + elif hasattr(self.Trans, '_module_sha1') and self.Trans._module_sha1: + Gen._struct_sha1_map[spec_name] = self.Trans._module_sha1 + return spec_name + def _resolve_base_kind(self, base_name: str, Gen: LlvmGeneratorMixin) -> str | None: """解析基类名称的类型种类 diff --git a/lib/core/Handles/HandlesExprCall.py b/lib/core/Handles/HandlesExprCall.py index 1b1c705..00de038 100644 --- a/lib/core/Handles/HandlesExprCall.py +++ b/lib/core/Handles/HandlesExprCall.py @@ -3068,8 +3068,10 @@ class ExprCallHandle(BaseHandle): base_name, cached_type_args, Gen, type_names=cached_type_names, declare_only=False ) - except Exception: - # 失败时恢复 declare_only 标记,避免后续调用跳过补发 + except Exception as _spec_err: + # 不静默吞特化异常:暴露到 Gen._generic_spec_error(Undefined method / + # len 失败时随报文输出),并恢复 declare_only 标记避免后续调用跳过补发。 + Gen._generic_spec_error = f'{type(_spec_err).__name__}: {_spec_err}' Gen._generic_class_specializations[spec_key] = (cached_name, True) Gen.builder = _saved_builder Gen.func = _saved_func diff --git a/lib/core/Handles/HandlesExprUtils.py b/lib/core/Handles/HandlesExprUtils.py index 7d8b087..f7cb924 100644 --- a/lib/core/Handles/HandlesExprUtils.py +++ b/lib/core/Handles/HandlesExprUtils.py @@ -181,8 +181,8 @@ class ExprUtils: # 泛型特化方法尚未生成时,先补发特化 try: self.Trans.ExprCallHandle._try_complete_generic_specialization(ClassName, op_name, Gen) - except Exception: - pass + except Exception as _spec_err: + Gen._generic_spec_error = f'{type(_spec_err).__name__}: {_spec_err}' FullMethodName = f'{ClassName}.{op_name}' if not Gen._has_function(FullMethodName): FullMethodName = f'{ClassName}.{op_name}__' @@ -224,8 +224,8 @@ class ExprUtils: # 缓存机制会从 declare_only 升级为完整特化),补发后重新检查 try: self.Trans.ExprCallHandle._try_complete_generic_specialization(best_candidates[0], op_name, Gen) - except Exception: - pass + except Exception as _spec_err: + Gen._generic_spec_error = f'{type(_spec_err).__name__}: {_spec_err}' _fn2: str = f'{best_candidates[0]}.{op_name}' if Gen._has_function(f'{best_candidates[0]}.{op_name}') else f'{best_candidates[0]}.{op_name}__' _f2: Any = Gen._get_function(_fn2) if _f2 is not None and getattr(_f2, 'blocks', []): diff --git a/lib/core/Handles/HandlesFunctions.py b/lib/core/Handles/HandlesFunctions.py index 0a5465b..87d1d71 100644 --- a/lib/core/Handles/HandlesFunctions.py +++ b/lib/core/Handles/HandlesFunctions.py @@ -591,8 +591,14 @@ class FunctionHandle(BaseHandle): # 避免过早触发完整特化(此时 MemManager.alloc 可能尚未注册, # 导致 __new__ 的 pool.alloc(48) 解析失败,IR 块未终止)。 # 完整特化由后续类实例化(L3688/L3753)触发 declare_only=False。 - spec_name2: str | None = self.Trans.ClassHandler._specialize_generic_class( - ret_class, ret_type_args, Gen, type_names=ret_type_names, declare_only=True) + try: + spec_name2: str | None = self.Trans.ClassHandler._specialize_generic_class( + ret_class, ret_type_args, Gen, type_names=ret_type_names, declare_only=True) + except Exception as _spec_err: + # 不静默吞特化异常:记录到 Gen._generic_spec_error 后再 + # 交给外层返回类型回退(否则只剩一个错误的 CInt 返回类型)。 + Gen._generic_spec_error = f'{type(_spec_err).__name__}: {_spec_err}' + raise if spec_name2 and spec_name2 in Gen.structs: ReturnTypeInfo = CTypeInfo() ReturnTypeInfo.BaseType = TypeSystem.CStruct(name=spec_name2) @@ -995,30 +1001,29 @@ class FunctionHandle(BaseHandle): elif isinstance(node.func, ast.Attribute): if isinstance(node.func.value, ast.Name) and node.func.value.id in type_map: replacement: str = type_map[node.func.value.id] - # 指针类型参数(如 list[CPtr[AST]] 中的 T)在表达式上下文中 - # 需替换为 `CPtr[AST]` 下标,使 __sizeof__ 等处理器能正确识别 - # 指针语义并直接返回 8,避免触发 struct 加载和重入编译 + # 指针类型参数(如 list[CPtr[AST]] 中的 T):表达式里 T 的唯一 + # 用法是 `T.__sizeof__()`,指针大小恒为 8。替换成**裸 CPtr** + # (而不是 CPtr[]):mangled 串含 '.'/'*',既不是 + # 合法 Name,也会让 __sizeof__ 的 Subscript 分支(只认泛型切片) + # 落空后把 CPtr 当变量求值 → "未定义名称: 'CPtr'"。 if cptr_params and node.func.value.id in cptr_params: - node.func.value = ast.Subscript( - value=ast.Name(id='CPtr', ctx=ast.Load()), - slice=self._make_name_node(replacement), - ctx=ast.Load() - ) + node.func.value = ast.Name(id='CPtr', ctx=ast.Load()) else: node.func.value = self._make_name_node(replacement) else: self._apply_type_map_to_expr(node.func.value, type_map, cptr_params) + elif isinstance(node.func, ast.Subscript): + # `T*(expr)` / `list[T](...)`:被调者/转换目标的切片也要替换。 + self._apply_type_map_to_expr(node.func, type_map, cptr_params) for arg in node.args: self._apply_type_map_to_expr(arg, type_map, cptr_params) elif isinstance(node, ast.Attribute): if isinstance(node.value, ast.Name) and node.value.id in type_map: replacement: str = type_map[node.value.id] if cptr_params and node.value.id in cptr_params: - node.value = ast.Subscript( - value=ast.Name(id='CPtr', ctx=ast.Load()), - slice=self._make_name_node(replacement), - ctx=ast.Load() - ) + # 同上:指针类型参数的表达式用法(T.__sizeof__ 等)只需 + # "指针语义",裸 CPtr 即指针大小 8;不要拼 mangled 串。 + node.value = ast.Name(id='CPtr', ctx=ast.Load()) else: node.value = ast.Name(id=replacement, ctx=ast.Load()) else: @@ -1041,7 +1046,14 @@ class FunctionHandle(BaseHandle): self._apply_type_map_to_expr(node.orelse, type_map, cptr_params) elif isinstance(node, ast.Subscript): self._apply_type_map_to_expr(node.value, type_map, cptr_params) - self._apply_type_map_to_expr(node.slice, type_map, cptr_params) + # `T*(expr)` 的指针转换在 AST 里是 Call(func=Subscript(CPtr, T)): + # 切片上的 T 必须整体替换成 `_make_replacement_node` 的产物(如 + # CPtr[.AST]),不能走 Name 分支直接改 id(mangled 串含 '.'/'*' + # 不是合法标识符),否则会留下裸 T → 生成幻影结构体 %".T"。 + if isinstance(node.slice, ast.Name) and node.slice.id in type_map: + node.slice = self._make_replacement_node(type_map[node.slice.id]) + else: + self._apply_type_map_to_expr(node.slice, type_map, cptr_params) elif isinstance(node, ast.Name): if node.id in type_map: node.id = type_map[node.id] @@ -1504,8 +1516,14 @@ class FunctionHandle(BaseHandle): # 返回类型解析只需 struct 定义,不需方法体;使用 declare_only=True # 避免过早触发完整特化(此时 MemManager.alloc 可能尚未注册, # 导致 __new__ 的 pool.alloc(48) 解析失败,IR 块未终止)。 - spec_name2: str | None = self.Trans.ClassHandler._specialize_generic_class( - ret_class, ret_type_args, Gen, type_names=ret_type_names, declare_only=True) + try: + spec_name2: str | None = self.Trans.ClassHandler._specialize_generic_class( + ret_class, ret_type_args, Gen, type_names=ret_type_names, declare_only=True) + except Exception as _spec_err: + # 不静默吞特化异常:记录到 Gen._generic_spec_error 后再 + # 交给外层返回类型回退(否则只剩一个错误的 CInt 返回类型)。 + Gen._generic_spec_error = f'{type(_spec_err).__name__}: {_spec_err}' + raise if spec_name2 and spec_name2 in Gen.structs: ReturnTypeInfo = CTypeInfo() ReturnTypeInfo.BaseType = TypeSystem.CStruct(name=spec_name2) diff --git a/lib/core/Handles/HandlesImports.py b/lib/core/Handles/HandlesImports.py index d16f0eb..0108220 100644 --- a/lib/core/Handles/HandlesImports.py +++ b/lib/core/Handles/HandlesImports.py @@ -4256,7 +4256,10 @@ class ImportHandle(BaseHandle): gc_spec: str | None = self.Trans.ClassHandler._specialize_generic_class( gc_base, gc_type_args, Gen, type_names=gc_type_names, declare_only=True) - except Exception: + except Exception as _spec_err: + # 不静默吞特化异常:记录现场,供后续 Undefined method / + # len 失败路径把真因带进报错。 + Gen._generic_spec_error = f'{type(_spec_err).__name__}: {_spec_err}' gc_spec = None Gen.module_sha1 = saved_module_sha1 self.Trans._module_sha1 = saved_trans_sha1 diff --git a/lib/core/Handles/HandlesTypeMerge.py b/lib/core/Handles/HandlesTypeMerge.py index ee75991..03b7867 100644 --- a/lib/core/Handles/HandlesTypeMerge.py +++ b/lib/core/Handles/HandlesTypeMerge.py @@ -4,7 +4,8 @@ if TYPE_CHECKING: from lib.core.translator import Translator from lib.core.Handles.HandlesBase import BaseHandle, CTypeHelper, CTypeInfo from lib.core.SymbolUtils import ( - IsListAnnotation, IsPtrSubscriptNode, IsCPtrMarkerNode) + IsListAnnotation, IsPtrSubscriptNode, IsCPtrMarkerNode, + MangleTypeArgByRegistry) from lib.core import TypeSystem import lib._bootstrap # noqa: F401 设置 sys.path(项目根 + lib) import vast as ast @@ -67,7 +68,7 @@ class HandlesTypeMerge(BaseHandle): Line: int = getattr(Node, 'lineno', 0) or 0 raise TypeError( f"[TPC] 不支持类型联合 'T1 | T2'(第 {Line} 行);" - f"类型注解只能是单一类型:指针请写 CPtr[T],修饰符请用关键字") + f"类型注解只能是单一类型:指针请写 T*(如 int*),修饰符请用关键字") Result = self._GetCTypeInfoInternal(Node) if isinstance(Result, CTypeInfo): Info = Result @@ -83,7 +84,7 @@ class HandlesTypeMerge(BaseHandle): Line: int = getattr(Node, 'lineno', 0) or 0 raise TypeError( f"[TPC] 不支持类型联合 'T1 | T2'(第 {Line} 行);" - f"类型注解只能是单一类型:指针请写 CPtr[T],修饰符请用关键字") + f"类型注解只能是单一类型:指针请写 T*(如 int*),修饰符请用关键字") LineNum = getattr(Node, 'lineno', None) if getattr(self.Trans, 'DebugFile', None): @@ -119,6 +120,9 @@ class HandlesTypeMerge(BaseHandle): Info.BaseType = TypeSystem.CVoid return Info if BuiltinCls == TypeSystem.CPtr: + # 裸 CPtr 仅来自内部类型字符串;源码注解写 CPtr / CPtr[...] + # 已在解析层拒绝(提示改写 T*)。内部 Subscript(CPtr, T) + # 仍由后续 Subscript 分支解析。 Info.PtrCount = 1 Info.BaseType = TypeSystem.CVoid return Info @@ -800,13 +804,18 @@ class HandlesTypeMerge(BaseHandle): arg_parts: list[str] = [] for elt in slice_node.elts: slice_res = self.Trans.ExprCallHandle._resolve_generic_slice_type(elt) - arg_parts.append(slice_res[0] if slice_res else self.Trans.ExprCallHandle._resolve_type_arg_str(elt)) + _raw_arg: str = slice_res[0] if slice_res else self.Trans.ExprCallHandle._resolve_type_arg_str(elt) + # 与 _mangle_generic_class_name 同口径(CInt → i32、 + # CFloat64T → f64):否则注解侧拼出 list[CInt], + # 实际特化名却是 list[i32] → 同名结构体分裂。 + arg_parts.append(MangleTypeArgByRegistry(_raw_arg)) # 必须用 '][' 分隔以匹配 _mangle_generic_class_name 的格式 # (class_name + '[' + ']['.join(mangled_args) + ']') type_arg_str = ']['.join(arg_parts) else: slice_res = self.Trans.ExprCallHandle._resolve_generic_slice_type(slice_node) - type_arg_str = slice_res[0] if slice_res else self.Trans.ExprCallHandle._resolve_type_arg_str(slice_node) + _raw_arg = slice_res[0] if slice_res else self.Trans.ExprCallHandle._resolve_type_arg_str(slice_node) + type_arg_str = MangleTypeArgByRegistry(_raw_arg) full_spec_name: str = f'{value_node.id}[{type_arg_str}]' ResultInfo.BaseType = TypeSystem.CStruct(name=full_spec_name) ResultInfo.Name = full_spec_name @@ -915,4 +924,4 @@ class HandlesTypeMerge(BaseHandle): Line: int = getattr(Node, 'lineno', 0) or 0 raise TypeError( f"[TPC] 不支持类型联合 'T1 | T2'(第 {Line} 行);" - f"类型注解只能是单一类型:指针请写 CPtr[T],修饰符请用关键字") + f"类型注解只能是单一类型:指针请写 T*(如 int*),修饰符请用关键字") diff --git a/lib/core/SymbolUtils.py b/lib/core/SymbolUtils.py index 9ec8875..3d4f7a0 100644 --- a/lib/core/SymbolUtils.py +++ b/lib/core/SymbolUtils.py @@ -2141,6 +2141,7 @@ _ANNOTATION_MARKER_ALIASES: dict[str, str] = { } # 指针/引用下标的 base 名:注解里出现这些名字即表示"指针层"(等价旧 CPtr 标记)。 +# CPtr 仅作为内部表示(`T*` 脱糖产物);源码写 CPtr[...] 已在解析层拒绝。 _POINTER_SUBSCRIPT_NAMES: frozenset[str] = frozenset({ 'CPtr', 'rawptr', 'CRef', 'CTempRef', 'CArrayPtr', 'CAutoPtr', }) @@ -2152,7 +2153,7 @@ def RejectTypeUnionAnnotation(node: ast.AST | None) -> None: Line: int = getattr(node, 'lineno', 0) or 0 raise TypeError( f"[TPC] 不支持类型联合 'T1 | T2'(第 {Line} 行);" - f"类型注解只能是单一类型:指针请写 CPtr[T],修饰符请用关键字") + f"类型注解只能是单一类型:指针请写 T*(如 int*),修饰符请用关键字") def AnnotationContainsMarker(node: ast.AST, MarkerName: str) -> bool: diff --git a/lib/core/TypeAnnotationResolver.py b/lib/core/TypeAnnotationResolver.py index 44d496b..045a84a 100644 --- a/lib/core/TypeAnnotationResolver.py +++ b/lib/core/TypeAnnotationResolver.py @@ -145,7 +145,7 @@ class TypeAnnotationResolver: Line: int = getattr(Node, 'lineno', 0) or 0 raise TypeError( f"[TPC] 不支持类型联合 'T1 | T2'(第 {Line} 行);" - f"类型注解只能是单一类型:指针请写 CPtr[T],修饰符请用关键字") + f"类型注解只能是单一类型:指针请写 T*(如 int*),修饰符请用关键字") if isinstance(Node, ast.Constant): Info: CTypeInfo = TypeAnnotationResolver._from_node_constant(Node, SymbolTable) elif isinstance(Node, ast.Name): @@ -575,6 +575,8 @@ class TypeAnnotationResolver: TypeObj = getattr(TypeSystem, TypeName, None) if isinstance(TypeObj, type) and issubclass(TypeObj, TypeSystem.CType) and TypeObj is not TypeSystem.CType: if TypeObj == TypeSystem.CPtr: + # CPtr 名仅来自内部类型字符串(builtins typedef 值等);源码 + # 注解写 CPtr / CPtr[...] 已在解析层拒绝,提示改写为 T*。 Result: CTypeInfo = CTypeInfo() Result.PtrCount = 1 Result.BaseType = TypeSystem.CVoid() @@ -790,6 +792,8 @@ class TypeAnnotationResolver: MarkerName = base.id elif isinstance(base, ast.Attribute): MarkerName = base.attr + # 指针/引用下标:CPtr 仅由 `T*` 脱糖产生(内部表示),源码写 CPtr[...] + # 已在解析层拒绝;rawptr / CRef / CTempRef / CArrayPtr / CAutoPtr 仍可直写。 if MarkerName in ('CPtr', 'rawptr'): SliceInfo: CTypeInfo = TypeAnnotationResolver.from_node(Node.slice, SymbolTable) Result: CTypeInfo = CTypeInfo() @@ -980,7 +984,7 @@ class TypeAnnotationResolver: Where: str = f'{File}:{Line}:{Col}' if File else f'第 {Line} 行第 {Col} 列' raise TypeError( f"[TPC] 类型注解不支持模块前缀 \"{FullName}\"({Where})。" - f"请改写为 \"{TypeName}\";指针写 CPtr[T] / rawptr[T]。") + f"请改写为 \"{TypeName}\";指针写 T*(如 int*)/ rawptr[T]。") # 解析 import 别名: 如 import vpsdk.window as window -> window -> vpsdk.window if ModulePath: resolved: bool = False diff --git a/lib/vast/ExprParser.py b/lib/vast/ExprParser.py index 9dc42ef..cc883e3 100644 --- a/lib/vast/ExprParser.py +++ b/lib/vast/ExprParser.py @@ -81,6 +81,32 @@ def _HexValOf(ch: str) -> int: return idx if idx < 16 else idx - 6 +def _FindSourceCPtr(Node: N.AST) -> N.AST | None: + """在注解 AST 中查找**源码书写**的 `CPtr`(裸名 / 限定名)。 + + 内部表示仍是 `Subscript(Name('CPtr'), T)`(`T*` 脱糖产物),但该合成 + Name 不带位置属性;源码中的 CPtr 一定带 lineno。据此只拒绝用户源码写法, + 不影响内部 AST。 + """ + if isinstance(Node, (N.Name, N.Attribute)): + Name: str = Node.id if isinstance(Node, N.Name) else Node.attr + if Name == 'CPtr' and hasattr(Node, 'lineno'): + return Node + for Field in getattr(Node, '_fields', ()): + Value = getattr(Node, Field, None) + if isinstance(Value, N.AST): + Found: N.AST | None = _FindSourceCPtr(Value) + if Found is not None: + return Found + elif isinstance(Value, list): + for Item in Value: + if isinstance(Item, N.AST): + Found = _FindSourceCPtr(Item) + if Found is not None: + return Found + return None + + def _HexEscapeVal(body: str, start: int, count: int) -> int: """取转义序列后 count 位十六进制的值(越界位置与非十六进制字符均按 0 计)。 @@ -1710,9 +1736,13 @@ class ExprParserMixin(TokenStream): 无法解析的后缀形态**响亮失败**(SyntaxError,含 行:列 + 源码片段 + 形态 + 正确写法示例),绝不静默丢弃:注解末尾若仍遗留未被 `_AtPtrSuffix` 接受的 - '*'/'**'(如 `a: int * 2` / `a: int* | CPtr`),判据拒绝消费即终止编译 + '*'/'**'(如 `a: int * 2`),判据拒绝消费即终止编译 (静默退化会产出错误的指针层数)。 + 用户源码不再接受 `CPtr[...]` / `CPtr` 指针写法(TypeError,提示改写成 + `T*`);内部 AST 仍以 `Subscript(Name('CPtr'), T)` 表示指针,`T*` 的 + 脱糖产物不受此限制。 + 值引用参数注解(仅形参位有效):前导 `&` 为 ref 模式、`&temp` 为 temp 模式, 合成 Subscript 形态 `CRef[T]` / `CTempRef[T]`(CRef/CTempRef 非真实类, 仅按名字识别;语义侧见 HandlesFunctions / FuncGen)。表达式位的 `&x` 不受影响。 @@ -1782,6 +1812,13 @@ class ExprParserMixin(TokenStream): raise SyntaxError( "[vast] 类型注解的修饰词后缺少类型(如 `x: const int`)。") return None + SourceCPtr: N.AST | None = _FindSourceCPtr(expr) + if SourceCPtr is not None: + Line: int = getattr(SourceCPtr, 'lineno', 0) or 0 + Col: int = (getattr(SourceCPtr, 'col_offset', 0) or 0) + 1 + raise TypeError( + f"[vast] 源码不支持 CPtr 指针写法(第 {Line} 行第 {Col} 列):" + f"指针请写 T*(如 int*)") if isinstance(expr, N.BinOp) and isinstance(expr.op, N.BitOr): def _is_none_literal(n: N.AST) -> bool: if isinstance(n, N.Constant): @@ -1797,7 +1834,7 @@ class ExprParserMixin(TokenStream): Line: int = getattr(expr, 'lineno', 0) or 0 raise TypeError( f"[vast] 不支持类型联合 'T1 | T2'(第 {Line} 行);" - f"类型注解只能是单一类型:指针请写 CPtr[T],修饰符请用关键字") + f"类型注解只能是单一类型:指针请写 T*(如 int*),修饰符请用关键字") if self._AtPtrSuffix(expr): expr = self._ConsumePtrSuffix(expr) tok: Token = self._Peek() @@ -1813,10 +1850,9 @@ class ExprParserMixin(TokenStream): f"或其后不是后缀的合法结束位置(注解结尾,或 ']' / ',' / ')' / ':' / '=')。" f"源码片段 {snippet!r}。" f"期望形态:`T*`(1 层)/ `T**`(2 层),T 可为裸名 / X / mod.Type / " - f"rawptr[T] / CPtr[T]。" - f"正确示例:`a: CPtr*` / `a: rawptr*` / `a: X**` / " - f"`a: rawptr[ast.AST*]` / `def f(p: int*) -> int**`。" - ) + f"rawptr[T]。" + f"正确示例:`a: int*` / `a: rawptr*` / `a: X**` / " + f"`a: rawptr[ast.AST*]` / `def f(p: int*) -> int**`。") if ref_mode is not None: expr = self._MakeRefMarker(expr, ref_mode, ref_tok) self._ApplyTypePrefix(expr, markers, attributes, byte_order, bit_width) diff --git a/lib/vast_c/_parser.pyd b/lib/vast_c/_parser.pyd index 76cc98e..61fa0e3 100644 Binary files a/lib/vast_c/_parser.pyd and b/lib/vast_c/_parser.pyd differ diff --git a/lib/vast_c/vast_c.c b/lib/vast_c/vast_c.c index 9c35a22..c854c23 100644 --- a/lib/vast_c/vast_c.c +++ b/lib/vast_c/vast_c.c @@ -3079,6 +3079,100 @@ static int IsNoneLiteralNode(PyObject *Node) { return 0; } +/* 注解 AST 中**源码书写**的 CPtr(裸名 / 限定名)节点(无则 NULL)。 + * 内部指针表示仍是 Subscript(Name('CPtr'), T)(`T*` 脱糖产物),但合成的 + * Name 不带位置属性(MakeTypeWrapperSubscript 只给 Subscript 定位);源码 + * 中的 CPtr 一定带 lineno。据此只拒绝用户源码写法,不影响内部 AST。 */ +static PyObject *FindSourceCPtr(PyObject *Node) { + if (Node == NULL || Node == Py_None) { + return NULL; + } + if (NodeIs(Node, "Name") || NodeIs(Node, "Attribute")) { + const char *Field = NodeIs(Node, "Name") ? "id" : "attr"; + PyObject *Name = PyObject_GetAttrString(Node, Field); + int Hit = 0; + if (Name == NULL) { + PyErr_Clear(); + } else { + if (PyUnicode_Check(Name) + && PyUnicode_CompareWithASCIIString(Name, "CPtr") == 0 + && PyObject_HasAttrString(Node, "lineno") == 1) { + Hit = 1; + } + Py_DECREF(Name); + } + if (Hit) { + return Node; + } + } + PyObject *Fields = PyObject_GetAttrString(Node, "_fields"); + if (Fields == NULL) { + PyErr_Clear(); + return NULL; + } + PyObject *Found = NULL; + Py_ssize_t N = PyTuple_Check(Fields) ? PyTuple_GET_SIZE(Fields) : 0; + for (Py_ssize_t I = 0; I < N && Found == NULL; I++) { + PyObject *FName = PyTuple_GET_ITEM(Fields, I); + if (!PyUnicode_Check(FName)) { + continue; + } + const char *FNameStr = PyUnicode_AsUTF8(FName); + if (FNameStr == NULL) { + PyErr_Clear(); + continue; + } + PyObject *Child = PyObject_GetAttrString(Node, FNameStr); + if (Child == NULL) { + PyErr_Clear(); + continue; + } + if (PyList_Check(Child) || PyTuple_Check(Child)) { + Py_ssize_t M = PySequence_Size(Child); + for (Py_ssize_t J = 0; J < M && Found == NULL; J++) { + PyObject *Item = PySequence_GetItem(Child, J); + if (Item == NULL) { + PyErr_Clear(); + continue; + } + Found = FindSourceCPtr(Item); + Py_DECREF(Item); + } + } else if (PyObject_HasAttrString(Child, "_fields") == 1) { + Found = FindSourceCPtr(Child); + } + Py_DECREF(Child); + } + Py_DECREF(Fields); + return Found; +} + +/* 按 AST 节点的 lineno/col_offset 找回对应 token(用于精确报错定位)。 */ +static Token *FindTokenAt(Parser *P, PyObject *Node) { + PyObject *LineObj = PyObject_GetAttrString(Node, "lineno"); + PyObject *ColObj = PyObject_GetAttrString(Node, "col_offset"); + if (LineObj == NULL || ColObj == NULL) { + PyErr_Clear(); + Py_XDECREF(LineObj); + Py_XDECREF(ColObj); + return NULL; + } + long Line = PyLong_AsLong(LineObj); + long Col = PyLong_AsLong(ColObj); + Py_DECREF(LineObj); + Py_DECREF(ColObj); + if (PyErr_Occurred()) { + PyErr_Clear(); + return NULL; + } + for (int I = 0; I < P->NTokens; I++) { + if (P->Tokens[I].Line == Line && P->Tokens[I].Col == Col) { + return &P->Tokens[I]; + } + } + return NULL; +} + /* 类型注解入口:置注解模式标志(对齐 Python ExprParser._ParseTypeAnnotation)。 * * 值引用参数注解(仅形参位有效,对齐纯 Python 端): @@ -3126,6 +3220,17 @@ static PyObject *ParseTypeAnnotation(Parser *P) { Py_XDECREF(Attributes); return NULL; } + /* 源码不再接受 CPtr[...] / CPtr 指针写法(内部 `T*` 脱糖产物无 lineno)。 */ + PyObject *SourceCPtr = FindSourceCPtr(Node); + if (SourceCPtr != NULL) { + Token *BadTok = FindTokenAt(P, SourceCPtr); + SyntaxErrorAt(P, BadTok != NULL ? BadTok : RefTok, + "指针请写 T*(如 int*):源码不再支持 CPtr[...] 指针写法"); + Py_DECREF(Node); + Py_XDECREF(Markers); + Py_XDECREF(Attributes); + return NULL; + } /* `T | None` / `None | T` 等价于 T;两侧同为 None 或均非 None 仍报错。 * 对齐 Pure Python ExprParser._ParseTypeAnnotation 的联合处理。 */ if (NodeIs(Node, "BinOp")) {