Skip to main content

Crate hermes_sema

Crate hermes_sema 

Source
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:

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 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 $SHBuiltin module protocol (visitModuleFactory / visitModuleExport / visitModuleImport and resolveCommonJSAST) — the three branches in resolver/calls.rs panic with a pointer at the C++ lines;
  • the lazy-compilation and eval entry points (resolveASTLazy, resolveASTInScope), which need SemContext’s parent/child tree and shared binding table — see resolve’s module doc;
  • visitProgram’s SaveAndRestore of globalScope_ (SemanticResolver.cpp:216-217): the assignment is ported, the restore is not. It only becomes observable once Program can recur, which is the same lazy/eval work as the previous bullet — see the comment at the site in resolver/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 — see hermes_sema::ids)
  • include/hermes/AST/Context.h (Keywords, line 168) and include/hermes/AST/Keywords.def (see hermes_sema::keywords)
  • lib/Sema/SemanticResolver.cpp / include/hermes/Sema/SemResolve.h (the validator/resolver, plus the two resolve entry 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 already NumericLiterals into a single NumericLiteral, 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. a BlockStatement), not the node that introduces the declaration’s binding — let x records the whole VariableDeclaration, not the VariableDeclarator/ Identifier inside it; SemanticResolver re-derives names/bindings from those nodes later.
dump
Port of hermes::sema::ASTPrinter and the untyped arm of semDump (lib/Sema/SemResolve.cpp:20-161,258-297). Byte-exact text dumper (the -dump-sema AST half, paired with crate::dump_context’s SemContextDumper for the SemContext half) 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, declared include/hermes/Sema/SemContext.h:694-753). This is the byte-exact text dumper the differential oracle depends on (hermesc -dump-sema output), 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 by hermes::sema::Decl, hermes::sema::LexicalScope, and hermes::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 opaque SemaId slot (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) and resolveASTForParser (cpp:299-310) — the two SemResolve.h entry points this crate has.
resolver
The resolution pass itself: the scope tree, Decl creation, identifier resolution, the validation diagnostics, and the compile-path AST rewrites.
sem_context
The result model of semantic analysis: Decl, LexicalScope, FunctionInfo and the SemContext that owns them.

Structs§

CompileOptions
What resolve_for_compile injects into the global scope before it resolves.
GlobalDefinitions
One file’s worth of ambient global declarations, as source text.
ResolveError
A resolution that reported at least one error.
ResolvedDiagnostic
One recorded diagnostic, re-exported because it appears in the façade’s signatures (ResolveError::diagnostics, ResolvedJS::diagnostics). Render one with hermes_support::render::render_diagnostic. It is the same type hermes_parser::ResolvedDiagnostic names. A fully resolved diagnostic handed to a DiagHandler. 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 SemContext holding the results, owned together.
SourceErrorManager
The source manager owning the parsed buffers, re-exported because ResolvedJS::source_manager returns one. A facade that owns source buffers and reports diagnostics against them. Rust port of hermes::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.