Expand description
Hermes semantic analysis (Rust port).
Parsing gives you a tree; this crate tells you what the names in it mean.
It builds the lexical scope tree, creates a Decl for every binding,
resolves every identifier to the declaration it names, runs the validation
the C++ SemanticResolver is responsible for, and — on the compile path —
performs the AST rewrites sema is allowed to make. The result is a
sem_context::SemContext.
§Quickstart
use hermes_parser::ast::node::Node;
use hermes_parser::{parse, ParseFlags};
use hermes_sema::sem_context::DeclKind;
let parsed = parse("var x = 1; x;", ParseFlags::default()).expect("parse");
let mut resolved = hermes_sema::resolve(parsed).expect("resolve");
// The reference `x` binds to the declaration `var x`.
let (name, decl) = resolved.with_program(|gc, program, sem| {
let body = match program {
Node::Program(p) => p.body,
_ => unreachable!("the root of a parse is always a Program"),
};
let expr = match body.iter().last().unwrap() {
Node::ExpressionStatement(e) => e.expression,
_ => unreachable!(),
};
match expr {
// `name` is an interned atom, so read it through the generated
// `name_str` accessor, which borrows the atom table under `gc`.
Node::Identifier(id) => {
(id.name_str(gc).to_string(), sem.get_expression_decl(id))
}
_ => unreachable!(),
}
});
assert_eq!(name, "x");
let decl = decl.expect("`x` must resolve");
// A top-level `var` in a script declares a property of the global object.
let kind = resolved.sem_context().decl(decl).kind;
assert_eq!(kind, DeclKind::GlobalProperty);crates/sema/examples/print_bindings.rs is that query applied to every
identifier in a file — the canonical use of the two crates — and it also
shows how a hermes_parser::ast::visitor::Visitor can hold the
&GCLock it needs for name_str in a field. (Give the lock its own
lifetime parameters there; GCLock<'ast, 'ctx> is invariant in 'ast, so
reusing the visitor’s 'gc for it does not compile.)
Text in the AST is interned rather than owned: name_str and the
try_<field>_str / <field>_str_lossy pair for string values are
documented in hermes_parser’s quickstart, and
hermes_parser::ast::context::GCLock::bytes remains the exact-bytes
accessor.
§The compile path, and the -dump-sema text
resolve() above is the parser path: no ambient declarations, no AST
rewrites — what a tooling embedder wants. resolve_for_compile is the
other entry point, the one hermesc itself uses: it declares the standard
globals and performs sema’s rewrites. ResolvedJS::to_sema_dump then
renders the result in hermesc -dump-sema’s exact format.
use hermes_parser::{parse, ParseFlags};
use hermes_sema::{resolve_for_compile, CompileOptions};
let parsed = parse("function f() { return 1; }", ParseFlags::default())
.expect("parse");
let mut resolved =
resolve_for_compile(parsed, &CompileOptions::default()).expect("resolve");
// Bytes, not a `String`: an identifier can be an unpaired surrogate, which
// the dumper writes as WTF-8.
let dump = resolved.to_sema_dump();
let text = String::from_utf8_lossy(&dump);
assert!(text.starts_with("SemContext\n"));
// `Math` and friends are declared because this is the compile path.
assert!(text.contains("'Math' UndeclaredGlobalProperty"));crates/sema/examples/resolve_and_dump.rs is this plus argument handling
and a --summary mode that walks the tree with the visitor instead of
dumping it.
The pieces a consumer touches:
resolve()/resolve_for_parser/resolve_for_compilereturningResolvedJS— the convenience façade overhermes_parser’shermes_parser::ParsedJS. It adds no analysis; anything it does not expose is reachable by callingresolve::resolve_ast/resolve::resolve_ast_for_parserdirectly, the waycrates/tools/src/bin/sema_dump.rsdoes.sem_context::SemContext— the results:Decl,LexicalScope,FunctionInfo, and the side tables keyed by AST node.ResolvedJS::to_sema_dump— thehermesc -dump-sematext, which is what this crate’s differential gate compares byte-for-byte. (The printers behind it live indumpanddump_context.)
The façade function resolve() and the module resolve share a
name, as parse would if the parser had a parse module: they are in
different namespaces, so hermes_sema::resolve(parsed) calls the function
and hermes_sema::resolve::resolve_ast names the entry point inside the
module. Both spellings are used in the examples above.
§Stability
This crate is pre-1.0 and the port it wraps is not finished (see the scope note below), so its ten public modules are not all equally settled. The stable surface — what 0.1.x means to keep source-compatible — is:
- the façade:
resolve(),resolve_for_parser,resolve_for_compile,ResolvedJS,ResolveError,CompileOptions,GlobalDefinitions; - the two low-level entry points in
resolve:resolve::resolve_astandresolve::resolve_ast_for_parser; - the result model:
sem_contextandids.
The other seven modules — resolver, decl_collector, ast_eval,
dump, dump_context, libhermes, keywords — are advanced /
port-internal. They are pub because the port’s own tools (sema-dump)
and integration tests drive them directly, not because their shape is
settled. They may change, or be demoted to pub(crate), in a 0.x bump.
Each says so in its own module doc.
§Scope of the port
The eager, untyped (non-FlowChecker) path of lib/Sema is ported and
gated byte-for-byte against hermesc -dump-sema. Still unported, and loud
rather than silent where they are reached:
- the
$SHBuiltinmodule protocol (visitModuleFactory/visitModuleExport/visitModuleImportandresolveCommonJSAST) — the three branches inresolver/calls.rspanic with a pointer at the C++ lines; - the lazy-compilation and
evalentry points (resolveASTLazy,resolveASTInScope), which needSemContext’s parent/child tree and shared binding table — seeresolve’s module doc; visitProgram’sSaveAndRestoreofglobalScope_(SemanticResolver.cpp:216-217): the assignment is ported, the restore is not. It only becomes observable onceProgramcan recur, which is the same lazy/evalwork as the previous bullet — see the comment at the site inresolver/mod.rs;- the FlowChecker itself, which is a separate C++ component and not part of this crate.
AST types (Node, Visitor, GCLock) come from hermes_parser::ast,
which is the same hermes-ast crate this one is built on, so depending on
hermes-parser and hermes-sema is enough.
Source of truth in the C++ tree:
include/hermes/Sema/SemContext.h(Decl,LexicalScope,FunctionInfo— seehermes_sema::ids)include/hermes/AST/Context.h(Keywords, line 168) andinclude/hermes/AST/Keywords.def(seehermes_sema::keywords)lib/Sema/SemanticResolver.cpp/include/hermes/Sema/SemResolve.h(the validator/resolver, plus the tworesolveentry points the façade wraps)
Modules§
- ast_
eval - Port of
hermes::sema::astFoldBinaryExpression/astFoldUnaryExpression(lib/Sema/ASTEval.h,lib/Sema/ASTEval.cpp, 95 lines total — read in full). Untyped constant folding: fold a binary or unary expression whose operand(s) are alreadyNumericLiterals into a singleNumericLiteral, computing the exact operations the C++ performs and nothing else. - decl_
collector - Port of
hermes::sema::DeclCollector(lib/Sema/DeclCollector.{h,cpp}, whole file): collects every declaration in every scope of a function (or static block) in a single upfront pass, so all of them can be hoisted without re-walking the AST. Declarations are recorded against the AST node that creates the scope they belong to (e.g. aBlockStatement), not the node that introduces the declaration’s binding —let xrecords the wholeVariableDeclaration, not theVariableDeclarator/Identifierinside it;SemanticResolverre-derives names/bindings from those nodes later. - dump
- Port of
hermes::sema::ASTPrinterand the untyped arm ofsemDump(lib/Sema/SemResolve.cpp:20-161,258-297). Byte-exact text dumper (the-dump-semaAST half, paired withcrate::dump_context’sSemContextDumperfor theSemContexthalf) that the differential oracle depends on — every space, quote, and (see below) quirk is transcribed straight from the C++<<chain it replaces. - dump_
context - Port of
hermes::sema::SemContextDumper(lib/Sema/SemContext.cpp:415-573, declaredinclude/hermes/Sema/SemContext.h:694-753). This is the byte-exact text dumper the differential oracle depends on (hermesc -dump-semaoutput), so every space and quote below is transcribed straight from the C++<<chain it replaces — do not “clean up” the formatting. - ids
- Typed ids for sema entities, backed by
hermes_ast::SemaId(lib.rs:16). Port of the identity discipline used byhermes::sema::Decl,hermes::sema::LexicalScope, andhermes::sema::FunctionInfo(include/hermes/Sema/SemContext.h): those C++ classes are allocated once and referenced thereafter by (typed) pointer; here they are referenced by a typed,u32-sized index instead, since the AST side only has an opaqueSemaIdslot (hermes_ast::node_child) to store one in. - keywords
- Port of
hermes::Keywords(include/hermes/AST/Context.h:168,include/hermes/AST/Keywords.def): a struct of pre-interned identifier atoms for strings sema compares against often ("arguments","eval","use strict", operator spellings like"+", …), so those comparisons are atom equality rather than byte comparison. - libhermes
- Port of
include/hermes/Runtime/Libhermes.h:13-72: the list of built-in symbols declared by the HermesVM runtime, as a JS source string. - resolve
- Port of
hermes::sema::resolveAST(lib/Sema/SemResolve.cpp:163-195) andresolveASTForParser(cpp:299-310) — the twoSemResolve.hentry points this crate has. - resolver
- The resolution pass itself: the scope tree,
Declcreation, identifier resolution, the validation diagnostics, and the compile-path AST rewrites. - sem_
context - The result model of semantic analysis:
Decl,LexicalScope,FunctionInfoand theSemContextthat owns them.
Structs§
- Compile
Options - What
resolve_for_compileinjects into the global scope before it resolves. - Global
Definitions - One file’s worth of ambient global declarations, as source text.
- Resolve
Error - A resolution that reported at least one error.
- Resolved
Diagnostic - One recorded diagnostic, re-exported because it appears in the façade’s
signatures (
ResolveError::diagnostics,ResolvedJS::diagnostics). Render one withhermes_support::render::render_diagnostic. It is the same typehermes_parser::ResolvedDiagnosticnames. A fully resolved diagnostic handed to aDiagHandler. All buffer lookups have already happened, so handlers are free of the source manager. - ResolvedJS
- A successful resolution: the arena, the resolved AST, and the
SemContextholding the results, owned together. - Source
Error Manager - The source manager owning the parsed buffers, re-exported because
ResolvedJS::source_managerreturns one. A facade that owns source buffers and reports diagnostics against them. Rust port ofhermes::SourceErrorManager.
Functions§
- resolve
- Resolve a parsed program, failing if resolution reported any error.
- resolve_
for_ compile - Resolve a parsed program the way the C++
resolveAST(SemResolve.cpp:163-195) does: the compile path. - resolve_
for_ parser - Resolve a parsed program the way the C++
resolveASTForParser(SemResolve.cpp:299-310) does, and hand back the result whether or not resolution reported errors.