hermes_sema/lib.rs
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#![forbid(unsafe_code)]
9
10//! Hermes semantic analysis (Rust port).
11//!
12//! Parsing gives you a tree; this crate tells you what the names in it mean.
13//! It builds the lexical scope tree, creates a `Decl` for every binding,
14//! resolves every identifier to the declaration it names, runs the validation
15//! the C++ `SemanticResolver` is responsible for, and — on the compile path —
16//! performs the AST rewrites sema is allowed to make. The result is a
17//! [`sem_context::SemContext`].
18//!
19//! # Quickstart
20//!
21//! ```
22//! use hermes_parser::ast::node::Node;
23//! use hermes_parser::{parse, ParseFlags};
24//! use hermes_sema::sem_context::DeclKind;
25//!
26//! let parsed = parse("var x = 1; x;", ParseFlags::default()).expect("parse");
27//! let mut resolved = hermes_sema::resolve(parsed).expect("resolve");
28//!
29//! // The reference `x` binds to the declaration `var x`.
30//! let (name, decl) = resolved.with_program(|gc, program, sem| {
31//! let body = match program {
32//! Node::Program(p) => p.body,
33//! _ => unreachable!("the root of a parse is always a Program"),
34//! };
35//! let expr = match body.iter().last().unwrap() {
36//! Node::ExpressionStatement(e) => e.expression,
37//! _ => unreachable!(),
38//! };
39//! match expr {
40//! // `name` is an interned atom, so read it through the generated
41//! // `name_str` accessor, which borrows the atom table under `gc`.
42//! Node::Identifier(id) => {
43//! (id.name_str(gc).to_string(), sem.get_expression_decl(id))
44//! }
45//! _ => unreachable!(),
46//! }
47//! });
48//! assert_eq!(name, "x");
49//! let decl = decl.expect("`x` must resolve");
50//!
51//! // A top-level `var` in a script declares a property of the global object.
52//! let kind = resolved.sem_context().decl(decl).kind;
53//! assert_eq!(kind, DeclKind::GlobalProperty);
54//! ```
55//!
56//! `crates/sema/examples/print_bindings.rs` is that query applied to every
57//! identifier in a file — the canonical use of the two crates — and it also
58//! shows how a [`hermes_parser::ast::visitor::Visitor`] can hold the
59//! `&GCLock` it needs for `name_str` in a field. (Give the lock its own
60//! lifetime parameters there; `GCLock<'ast, 'ctx>` is invariant in `'ast`, so
61//! reusing the visitor's `'gc` for it does not compile.)
62//!
63//! Text in the AST is interned rather than owned: `name_str` and the
64//! `try_<field>_str` / `<field>_str_lossy` pair for string *values* are
65//! documented in [`hermes_parser`]'s quickstart, and
66//! [`hermes_parser::ast::context::GCLock::bytes`] remains the exact-bytes
67//! accessor.
68//!
69//! ## The compile path, and the `-dump-sema` text
70//!
71//! [`resolve()`] above is the *parser* path: no ambient declarations, no AST
72//! rewrites — what a tooling embedder wants. [`resolve_for_compile`] is the
73//! other entry point, the one `hermesc` itself uses: it declares the standard
74//! globals and performs sema's rewrites. [`ResolvedJS::to_sema_dump`] then
75//! renders the result in `hermesc -dump-sema`'s exact format.
76//!
77//! ```
78//! use hermes_parser::{parse, ParseFlags};
79//! use hermes_sema::{resolve_for_compile, CompileOptions};
80//!
81//! let parsed = parse("function f() { return 1; }", ParseFlags::default())
82//! .expect("parse");
83//! let mut resolved =
84//! resolve_for_compile(parsed, &CompileOptions::default()).expect("resolve");
85//!
86//! // Bytes, not a `String`: an identifier can be an unpaired surrogate, which
87//! // the dumper writes as WTF-8.
88//! let dump = resolved.to_sema_dump();
89//! let text = String::from_utf8_lossy(&dump);
90//! assert!(text.starts_with("SemContext\n"));
91//! // `Math` and friends are declared because this is the compile path.
92//! assert!(text.contains("'Math' UndeclaredGlobalProperty"));
93//! ```
94//!
95//! `crates/sema/examples/resolve_and_dump.rs` is this plus argument handling
96//! and a `--summary` mode that walks the tree with the visitor instead of
97//! dumping it.
98//!
99//! The pieces a consumer touches:
100//! - [`resolve()`] / [`resolve_for_parser`] / [`resolve_for_compile`] returning
101//! [`ResolvedJS`] — the convenience façade over `hermes_parser`'s
102//! [`hermes_parser::ParsedJS`]. It adds no analysis; anything it does not
103//! expose is reachable by calling [`resolve::resolve_ast`] /
104//! [`resolve::resolve_ast_for_parser`] directly, the way
105//! `crates/tools/src/bin/sema_dump.rs` does.
106//! - [`sem_context::SemContext`] — the results: `Decl`, `LexicalScope`,
107//! `FunctionInfo`, and the side tables keyed by AST node.
108//! - [`ResolvedJS::to_sema_dump`] — the `hermesc -dump-sema` text, which is
109//! what this crate's differential gate compares byte-for-byte. (The
110//! printers behind it live in [`dump`] and [`dump_context`].)
111//!
112//! The façade function [`resolve()`] and the module [`mod@resolve`] share a
113//! name, as `parse` would if the parser had a `parse` module: they are in
114//! different namespaces, so `hermes_sema::resolve(parsed)` calls the function
115//! and `hermes_sema::resolve::resolve_ast` names the entry point inside the
116//! module. Both spellings are used in the examples above.
117//!
118//! # Stability
119//!
120//! This crate is pre-1.0 and the port it wraps is not finished (see the scope
121//! note below), so its ten public modules are not all equally settled. The
122//! **stable** surface — what 0.1.x means to keep source-compatible — is:
123//!
124//! - the façade: [`resolve()`], [`resolve_for_parser`], [`resolve_for_compile`],
125//! [`ResolvedJS`], [`ResolveError`], [`CompileOptions`],
126//! [`GlobalDefinitions`];
127//! - the two low-level entry points in [`mod@resolve`]:
128//! [`resolve::resolve_ast`] and [`resolve::resolve_ast_for_parser`];
129//! - the result model: [`sem_context`] and [`ids`].
130//!
131//! The other seven modules — [`resolver`], [`decl_collector`], [`ast_eval`],
132//! [`dump`], [`dump_context`], [`libhermes`], [`keywords`] — are **advanced /
133//! port-internal**. They are `pub` because the port's own tools (`sema-dump`)
134//! and integration tests drive them directly, not because their shape is
135//! settled. They may change, or be demoted to `pub(crate)`, in a 0.x bump.
136//! Each says so in its own module doc.
137//!
138//! # Scope of the port
139//!
140//! The eager, untyped (non-FlowChecker) path of `lib/Sema` is ported and
141//! gated byte-for-byte against `hermesc -dump-sema`. Still unported, and loud
142//! rather than silent where they are reached:
143//!
144//! - the `$SHBuiltin` module protocol (`visitModuleFactory` / `visitModuleExport`
145//! / `visitModuleImport` and `resolveCommonJSAST`) — the three branches in
146//! `resolver/calls.rs` panic with a pointer at the C++ lines;
147//! - the lazy-compilation and `eval` entry points (`resolveASTLazy`,
148//! `resolveASTInScope`), which need `SemContext`'s parent/child tree and
149//! shared binding table — see [`mod@resolve`]'s module doc;
150//! - `visitProgram`'s `SaveAndRestore` of `globalScope_`
151//! (`SemanticResolver.cpp:216-217`): the assignment is ported, the restore
152//! is not. It only becomes observable once `Program` can recur, which is
153//! the same lazy/`eval` work as the previous bullet — see the comment at
154//! the site in `resolver/mod.rs`;
155//! - the FlowChecker itself, which is a separate C++ component and not part
156//! of this crate.
157//!
158//! AST types (`Node`, `Visitor`, `GCLock`) come from `hermes_parser::ast`,
159//! which is the same `hermes-ast` crate this one is built on, so depending on
160//! `hermes-parser` and `hermes-sema` is enough.
161//!
162//! Source of truth in the C++ tree:
163//! - `include/hermes/Sema/SemContext.h` (`Decl`, `LexicalScope`,
164//! `FunctionInfo` — see `hermes_sema::ids`)
165//! - `include/hermes/AST/Context.h` (`Keywords`, line 168) and
166//! `include/hermes/AST/Keywords.def` (see `hermes_sema::keywords`)
167//! - `lib/Sema/SemanticResolver.cpp` / `include/hermes/Sema/SemResolve.h`
168//! (the validator/resolver, plus the two `resolve` entry points the façade
169//! wraps)
170
171#![warn(missing_docs)]
172
173pub mod ast_eval;
174// Private for the same reason its C++ counterpart is declared in the internal
175// `lib/Sema/SemanticResolver.h` rather than in `SemResolve.h` — see the
176// module's own doc.
177mod check_implicit_return;
178pub mod decl_collector;
179pub mod dump;
180pub mod dump_context;
181pub mod ids;
182pub mod keywords;
183pub mod libhermes;
184mod linearize;
185pub mod resolve;
186pub mod resolver;
187pub mod sem_context;
188
189/// The façade module is private: its items are re-exported here so each has
190/// exactly one path in the docs, matching `hermes_parser::facade`.
191mod facade;
192
193pub use facade::{
194 resolve, resolve_for_compile, resolve_for_parser, CompileOptions,
195 GlobalDefinitions, ResolveError, ResolvedJS,
196};
197
198/// One recorded diagnostic, re-exported because it appears in the façade's
199/// signatures ([`ResolveError::diagnostics`], [`ResolvedJS::diagnostics`]).
200/// Render one with `hermes_support::render::render_diagnostic`. It is the
201/// same type `hermes_parser::ResolvedDiagnostic` names.
202pub use hermes_support::diag::ResolvedDiagnostic;
203
204/// The source manager owning the parsed buffers, re-exported because
205/// [`ResolvedJS::source_manager`] returns one.
206pub use hermes_support::manager::SourceErrorManager;