ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Hermes Rust 编译器诊断体系解析:hermes_diagnostics 的类型设计与 miette 集成实践

Hermes Rust 编译器诊断体系解析:hermes_diagnostics 的类型设计与 miette 集成实践 语言运行时编译器移动开发【免费下载链接】hermesA JavaScript engine optimized for running React Native.项目地址https://gitcode.com/gh_mirrors/hermes/hermes点击查看免费下载本篇文章围绕 Hermes 开源仓库中 Rust 侧编译管线的诊断基础设施——hermes_diagnosticscrate 展开系统讲解它如何用一套「分类化严重程度 通用诊断表示」替代直接依赖 miette / lsp_types 的常见做法并介绍它与miette漂亮打印、hermes_estree源码定位以及hermes_parser、hermes_semantic_analysis等下游 crate 的协作方式。读完本文你将理解这套诊断类型的内部数据结构、四级严重程度语义、构建与注解 API以及如何在自己的工具链中复用它。一、crate 定位Rust 编译管线的统一诊断出口hermes_diagnostics是 Hermes 仓库unsupported/hermes/crates/工作区中的基础 crate 之一位于 unsupported/hermes/crates/hermes_diagnostics/。它在工作区中被多个上游 crate 依赖是整个 Rust 侧编译管线的「诊断出口」hermes_parser 将解析错误包装为Diagnostic返回hermes_semantic_analysis 在作用域分析中产生诊断hermes_estree_codegen 的代码生成逻辑也通过DiagnosticsResult/Diagnostic上报问题。其官方定位见 README.md是提供表示编译器诊断的类型包含一种通用的诊断表示general-purpose representation可附带相关信息related information并能转换成miette::Diagnostic从而利用 miette 对错误的漂亮打印pretty printing。这里的关键词是「通用表示」上游 crate 只面向hermes_diagnostics这一套类型编程而具体的展示方式终端彩色输出、LSP 推送等由消费方决定从而把「诊断产生」与「诊断渲染」彻底解耦。二、设计动机为什么不用现成的 miette / lsp_typesREADME 明确指出本 crate 与 miette、lsp_types 等其他诊断库的核心差异与 miette、lsp_types 等诊断库不同这里的错误严重程度severities被分类允许不同工具以不同级别报告它们。例如某个工具可以选择把 todo 错误报告出来也可以选择忽略它们。这正是本项目与「直接以 miette 为最终类型」的方案之间的关键分界点。miette 的Severity只有Error/Warning/Advice三档LSP 的DiagnosticSeverity也只有 Error / Warning / Information / Hint 四档它们描述的是「这条消息有多严重」而 Hermes 编译器面临的问题往往是**「这条错误属于哪一类」**——是尚未实现的特性、语法合法但不支持的写法、真正的语法错误还是编译器内部错误。只有先完成「分类」不同的前端工具CLI、LSP、测试框架才能根据自身语境决定将其映射为哪种级别的展示。因此hermes_diagnostics站在「诊断产生端」miette 站在「诊断展示端」二者通过impl miette::Diagnostic for Diagnostic桥接互不越界。Cargo.toml 中的注释也印证了这一设计意图# TODO: consider extracting a separate hermes_miette crate which does # the translation from hermes_diagnostics::Diagnostic to miette::Diagnostic即未来甚至可能把「向 miette 转换」这一层单独抽成 crate进一步强化职责分离。三、核心数据结构速览src/lib.rs与src/diagnostic.rs共同定义了整套类型。先看最常用的别名与容器类型定义用途DiagnosticsVecDiagnostic一批诊断通常代表一次编译/解析过程收集到的全部问题DiagnosticsResultTResultT, Diagnostics可携带多条诊断的 Result 别名是下游 crate 最常用的返回类型WithDiagnosticsT{ item: T, diagnostics: VecDiagnostic }把「产物」与「伴随诊断」打包FromWithDiagnosticsT for ResultT, Diagnostics允许在其上做类型转换诊断为空则取Ok(item)否则Err(diagnostics)diagnostics_result(result, diagnostics)函数与WithDiagnostics等价的过程式版本同样按诊断是否为空决定返回Ok还是Err其中WithDiagnostics的价值在于有些流程例如解析即使出现了警告级问题产物本身依然可用这个容器允许调用方「先产出、再决定」而不是在第一个问题上立即失败。四、四级严重程度DiagnosticSeverity 枚举diagnostic.rs 中定义了带有thiserror派生错误消息的枚举这是整个 crate 分类设计的落点#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Error)] pub enum DiagnosticSeverity { /// A feature that is intended to work but not yet implemented #[error(Not implemented)] Todo, /// Syntax that is valid but intentionally not supported #[error(Unsupported)] Unsupported, /// Invalid syntax #[error(Invalid JavaScript)] InvalidSyntax, /// Internal compiler error (ICE) #[error(Internal error)] Invariant, }每一档的语义与典型场景TodoNot implemented设计上打算支持、但当前尚未实现的特性。工具可以选择「直接报告」或「静默忽略」。典型场景是 JavaScript 新语法如装饰器、新运算符尚未落地时的占位提示。UnsupportedUnsupported语法本身合法但编译器有意不支持。例如 Hermes 面向嵌入式场景刻意裁剪的某些 ES 特性。这与 Todo 的区别在于Unsupported 是「最终决策」Todo 是「暂态」。InvalidSyntaxInvalid JavaScript真正的语法错误用户代码写错了。InvariantInternal error编译器内部错误ICEInternal Compiler Error通常意味着代码库自身有 bug而非用户代码问题。这个枚举派生自thiserror::Error因此每个变体都自带人类可读的错误文案Not implemented 等可直接参与错误链传播同时派生了PartialOrd / Ord便于按严重度排序或比较。从源码结构看这个分类设计还允许同一工具在不同调用上下文里做差异化映射例如 LSP 集成可以把Todo映射为 Hint、Unsupported映射为 Warning、InvalidSyntax映射为 Error而 CLI 批量检查脚本可能直接忽略Todo只针对InvalidSyntax失败。这正是 README 所说「不同工具以不同级别报告它们」的实现基础。五、Diagnostic 结构构建、注解与访问5.1 内部布局Diagnostic是一个胖指针包装thin wrapper over boxed data#[derive(Debug)] pub struct Diagnostic(BoxDiagnosticData);内部数据DiagnosticData包含五个字段struct DiagnosticData { message: Boxdyn DiagnosticDisplay, // 人类可读的错误消息 span: SourceSpan, // 主定位miette::SourceSpan related_information: VecDiagnosticRelatedInformation, // 次级定位 severity: DiagnosticSeverity, // 上述四级分类 data: VecBoxdyn DiagnosticDisplay, // 供 LSP code actions 使用的附加数据 }message与data使用 trait 对象Boxdyn DiagnosticDisplay而不是 String是为了支持**「持有结构化数据、按需惰性格式化为文本」**的消息类型见下文第六节。span直接采用 miette 的SourceSpan但构造入口统一接收hermes_estree::SourceRange通过source_span_from_range做换算以range.start为偏移、以range.end - range.start为长度。也就是说上游只要持有 ESTree 源码范围就能直接生成诊断无需关心 miette 的偏移语义。data字段对应 LSP 诊断对象中的data字段用于 code actions 透传上下文相关注释给出了对 LSP 规范的引用说明该 crate 的字段设计是「以 LSP Diagnostic 为蓝本、按需裁剪」的。5.2 构造器与注解 API四种严重程度各有一个对应的构造器签名统一为(message, range)Diagnostic::todo(..., range) // - DiagnosticSeverity::Todo Diagnostic::unsupported(..., range) // - DiagnosticSeverity::Unsupported Diagnostic::invalid_syntax(..., range)// - DiagnosticSeverity::InvalidSyntax Diagnostic::invariant(..., range) // - DiagnosticSeverity::Invariant在此基础上annotate(message, range)为诊断追加次级定位与消息对应 LSP 的relatedInformation常用于「重复定义」这类错误主消息定位到第二次定义处annotate追加的信息定位到第一次定义处。调用可链式叠加多个注解。let diag Diagnostic::invalid_syntax(Duplicate identifier x, second_range) .annotate(First declared here, first_range);此外还提供了一组访问器与便捷输出message() - impl DiagnosticDisplay、into_message()取消息借用或消费span() - SourceSpan取主定位severity()、get_data()、related_information()分别取分类、附加数据、次级信息print_without_source()不依赖源码文本的纯文本渲染格式为message:span次级信息以[related N] message:span逐条列出适合在无源码上下文的场景如单元测试、日志快速落盘。Diagnostic同时实现了Display输出消息文本与std::error::Error因此可以无缝接入 Rust 的?错误传播体系。六、DiagnosticDisplay可扩展的惰性消息 traitcrate 定义了消息专用 trait#[typetag::serialize(tag type)] pub trait DiagnosticDisplay: Debug Display Send Sync {}设计要点惰性格式化实现方可以持有结构化数据例如「引用的符号名 定义位置」在Display时才拼装完整文案避免过早字符串化丢失类型信息自动实现借助 blanket impl任何满足Debug Display Send Sync typetag::Serialize的类型都会自动获得该 trait实现者无需手动 impl#[typetag::serialize] implT DiagnosticDisplay for T where T: Debug Display Send Sync typetag::Serialize {}可序列化typetag使 trait 对象具备序列化能力按type标签标记具体类型为跨进程传递诊断、持久化诊断数据提供了可能。由于Send Sync是 trait 的硬性约束Diagnostic整体是线程安全的——源码通过static_assertions在编译期固化这一保证assert_impl_all!(Diagnostic: Send, Sync);这保证了诊断可以跨线程收集、汇总例如并行解析多个文件后集中上报而不会引入未定义行为。七、与 miette 的桥接漂亮的终端错误渲染Diagnostic实现了miette::Diagnostic这是 README 承诺的「利用 miette 漂亮打印」的关键labels()把主 span 与所有related_information的 span 转换成miette::LabeledSpan。若存在次级信息则输出多个带标签的标注否则退化为只标注主 span标签即消息文本。渲染效果是在源码上以箭头/下划线标注出问题位置及每处注解。help()将消息文本作为 miette 的 help 提示返回。因此只要持有Diagnostic就能直接喂给miette::Report获得带源码上下文、行号、彩色标注的终端输出无需任何手工转换。而「生成诊断」与「选择渲染方案」两个环节彻底分离同一诊断在 CLI 里走 miette 彩色打印在 LSP 里转换为lsp_types::Diagnostic在测试里用print_without_source()做断言。八、invariant 辅助函数与诊断聚合lib.rs 暴露了一个便捷函数用于「条件不满足则产生诊断」的常见模式pub fn invariantF(cond: bool, f: F) - Result(), Diagnostic where F: Fn() - Diagnostic, { if cond { Ok(()) } else { Err(f()) } }用法示例校验某个编译前提如「函数体非空」不满足时通过闭包惰性构造Invariant诊断并返回Err由于闭包是惰性求值条件满足时不会产生任何诊断开销。此外impl FromDiagnostic for Diagnostics允许单条诊断通过.into()直接升级为VecDiagnostic方便在不同返回类型之间切换。九、仓库内的真实使用场景9.1 hermes_parser解析错误包装hermes_parser/src/lib.rs 展示了最典型的使用方式——把底层 C 解析器的错误消息转换为InvalidSyntax诊断use hermes_diagnostics::Diagnostic; // ... pub fn parse(source: str, _file: str, flags: ParserFlags) - ResultParseResult, VecDiagnostic { let result HermesParser::parse(flags, buf); if result.has_errors() { return Err(result.messages().iter().map(|diag| { let message utf8_with_surrogates_to_string(diag.message.as_slice()).unwrap(); let start convert_smloc(cx, diag.loc) as u32; Diagnostic::invalid_syntax(message, SourceRange { start, end: start 1 }) }).collect()); } // ... }这里将底层原生解析器的每条消息映射为Diagnostic::invalid_syntax并把源码位置SourceRange一起打包返回Result_, VecDiagnostic供上层统一处理。9.2 hermes_semantic_analysis语义诊断hermes_semantic_analysis/src/analyzer.rs 在作用域/引用分析中引入hermes_diagnostics::Diagnostic语义检查阶段的「未声明变量」「重复声明」等问题都可以用Todo/InvalidSyntax/Unsupported分类表达与解析阶段共用同一套类型体系。9.3 hermes_estree_codegen生成代码中的诊断hermes_estree_codegen/src/codegen.rs 中导出的生成代码直接使用hermes_diagnostics::DiagnosticsResult与Diagnostic说明该 crate 已深度嵌入代码生成的类型签名是工作区内的事实标准诊断类型。十、如何在本仓库中查看与使用本 crate 是unsupported/hermesCargo 工作区的一员工作区 Cargo.toml成员通过crates/*通配符注册hermes_diagnostics同时在[workspace.dependencies]中以路径依赖声明。若要在该工作区内复用此 crate只需在目标 crate 的Cargo.toml中添加[dependencies] hermes_diagnostics { workspace true }依赖项为hermes_estree提供SourceRange、miette提供SourceSpan与渲染 trait、thiserror派生严重度枚举的错误实现、static_assertions编译期线程安全断言与typetagtrait 对象序列化见 Cargo.toml。值得注意的两个 TODO 与限制hermes_estree依赖被标注为「为了SourceRange而引入的较重依赖」未来计划把SourceRange抽取为独立 crate以解耦诊断与完整 ESTree 类型体系向 miette 的转换目前内嵌在本 crate 中未来可能抽成独立的hermes_miettecrate。十一、总结hermes_diagnostics以「分类化严重程度」为核心创新用Todo / Unsupported / InvalidSyntax / Invariant四级分类取代传统的错误/警告二分法配合SourceRange定位、annotate次级注解、DiagnosticDisplay惰性消息 trait 与miette::Diagnostic桥接为 Hermes Rust 编译管线提供了一套「产生、聚合、传播、渲染」职责分离的完整诊断方案。无论是解析器hermes_parser、语义分析hermes_semantic_analysis还是代码生成hermes_estree_codegen都能以统一方式产出诊断再由 CLI、LSP 或测试框架按自身语境决定呈现级别——这正是嵌入式 JS 引擎在「面向不同使用场景」时需要的关键灵活性。赞分享语言运行时编译器移动开发【免费下载链接】hermesA JavaScript engine optimized for running React Native.项目地址https://gitcode.com/gh_mirrors/hermes/hermes点击查看免费下载相关推荐ESP32智能小车终极指南3小时打造你的第一台自动避障机器人ESP32智能小车终极指南3小时打造你的第一台自动避障机器人 还在为昂贵的机器人套件望而却步想要亲手打造一台智能小车却不知从何开始今天我要与你分享一个超实嵌入式物联网驱动开发深入解析 Rust 编译错误 E0223ambiguous associated type 的成因、修复与编译器诊断实现深入解析 Rust 编译错误 E0223ambiguous associated type 的成因、修复与编译器诊断实现 导读 本文围绕 rustc 错误代码编程语言编译器语言运行时标准库Rust 编译器错误码 E0087 详解多余的泛型类型参数与 E0107 诊断机制Rust 编译器错误码 E0087 详解多余的泛型类型参数与 E0107 诊断机制 本文围绕 Rust 编译器历史错误码 E0087函数被传入过多类型参数编程语言编译器语言运行时标准库上一篇CN-GreenLumaGUI零门槛单文件的 Steam 游戏解锁图形化工具下一篇ANI-RSS 自动追番工具如何安装与快速上手一步步完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表