pub struct ResolvedJS { /* private fields */ }Expand description
A successful resolution: the arena, the resolved AST, and the
SemContext holding the results, owned together.
Produced by resolve, resolve_for_parser or resolve_for_compile,
each of which consumes the ParsedJS it resolves. Read the tree with
its semantic information through with_program, or
dump both with to_sema_dump.
Not Send, for the same reason ParsedJS is not: the arena uses
Cell/UnsafeCell and the GCLock guarding it is thread-local by design.
(The name keeps the port’s ParsedJS casing rather than Rust’s
ResolvedJs; that is deliberate, for consistency inside the port.)
Implementations§
Source§impl ResolvedJS
impl ResolvedJS
Sourcepub fn with_program<R, F>(&mut self, f: F) -> R
pub fn with_program<R, F>(&mut self, f: F) -> R
Run f with the arena locked, the resolved root node, and the
SemContext in hand.
This is the read path. The SemContext comes along because that is
the point of resolution: given an hermes_ast::node::Identifier in
the tree, SemContext::get_expression_decl /
SemContext::get_declaration_decl give the crate::ids::DeclId it
binds to, and SemContext::decl gives that declaration.
References into the arena cannot escape the closure — their lifetime
ends with the lock — so return owned data instead. The one thing that
can escape is a NodeRc, which is refcounted rather than borrowed;
dropping this ResolvedJS while such a handle is still alive panics
inside Context::drop.
The bound is higher-ranked because Node is invariant in its
lifetime: a walker (hermes_ast::visitor::Visitor) needs the node
reference and the node’s own lifetime to be the same 'gc, which only
a for<'gc> closure can promise. A visitor that keeps the &GCLock in
a field must give the lock its own lifetime parameters rather than
reusing 'gc — GCLock<'ast, 'ctx> is invariant in 'ast, so the two
cannot be equated; crates/sema/examples/print_bindings.rs shows the
pattern and the error it avoids.
§Why &mut self for a read
This locks the arena, and
Context::lock takes
&mut self: the GCLock holds a &mut Context, which is what stops
Context::gc — the mark-and-sweep
that would invalidate every outstanding &Node — from running while
the tree is being read. &mut therefore means “exclusive view of the
arena”, not “the AST or the SemContext is modified”; the SemContext
is in fact handed to f as &. To share results, collect owned data
inside the closure. (sem_context needs no lock
and does take &self.)
§Panics
Panics if another GCLock is active on this thread — in particular
if with_program is called from inside another with_program.
Examples found in repository?
98fn print_summary(resolved: &mut ResolvedJS) {
99 use hermes_parser::ast::node::Node;
100 use hermes_parser::ast::visitor::Visitor;
101
102 /// Counts identifier *expressions* by whether they resolved.
103 struct Counter<'a> {
104 sem: &'a hermes_sema::sem_context::SemContext,
105 resolved: usize,
106 unresolved: usize,
107 }
108 impl<'gc> Visitor<'gc> for Counter<'_> {
109 fn visit_node(&mut self, node: &'gc Node<'gc>) {
110 if let Node::Identifier(id) = node {
111 match self.sem.get_expression_decl(id) {
112 Some(_) => self.resolved += 1,
113 None => self.unresolved += 1,
114 }
115 }
116 node.visit_children(self);
117 }
118 }
119
120 let (resolved_ids, unresolved_ids) =
121 resolved.with_program(|_gc, root, sem| {
122 let mut c = Counter {
123 sem,
124 resolved: 0,
125 unresolved: 0,
126 };
127 c.visit_node(root);
128 (c.resolved, c.unresolved)
129 });
130
131 let sem = resolved.sem_context();
132 println!("functions: {}", sem.functions_len());
133 println!("resolved references: {resolved_ids}");
134 println!("other identifiers: {unresolved_ids}");
135}More examples
126fn main() {
127 let (name, source) = match std::env::args().nth(1) {
128 Some(path) => match std::fs::read_to_string(&path) {
129 Ok(s) => (path, s),
130 Err(e) => {
131 eprintln!("print_bindings: cannot read '{path}': {e}");
132 std::process::exit(1);
133 }
134 },
135 None => ("<builtin>".to_string(), SOURCE.to_string()),
136 };
137
138 // Step 1: parse. `ParseFlags::default()` is plain ECMAScript.
139 let parsed = match parse_named(&source, &name, ParseFlags::default()) {
140 Ok(parsed) => parsed,
141 Err(e) => {
142 // `messages()` strings are already newline-terminated.
143 for m in e.messages() {
144 eprint!("{m}");
145 }
146 std::process::exit(2);
147 }
148 };
149
150 // Step 2: resolve. The compile path, so the standard globals exist and an
151 // undeclared `console` comes back as `UndeclaredGlobalProperty` rather
152 // than as nothing at all; `hermes_sema::resolve` is the parser path.
153 let mut resolved =
154 match resolve_for_compile(parsed, &CompileOptions::default()) {
155 Ok(resolved) => resolved,
156 Err(e) => {
157 for m in e.messages() {
158 eprint!("{m}");
159 }
160 std::process::exit(2);
161 }
162 };
163
164 // Step 3: walk. References into the arena die with the lock, so the
165 // visitor collects owned `String`s and hands them back out.
166 let rows = resolved.with_program(|gc, root, sem| {
167 let mut printer = BindingPrinter {
168 gc,
169 sem,
170 rows: Vec::new(),
171 };
172 printer.visit_node(root);
173 printer.rows
174 });
175
176 println!("{}: {} identifiers", name, rows.len());
177 for (name, role, binding) in rows {
178 println!(" {name:<12} {role:<5} {binding}");
179 }
180}Sourcepub fn sem_context(&self) -> &SemContext
pub fn sem_context(&self) -> &SemContext
The resolution results, for the queries that do not need the AST —
walking the scope tree, or reading a FunctionInfo reached from a
crate::ids::FunctionInfoId obtained inside
with_program.
Examples found in repository?
98fn print_summary(resolved: &mut ResolvedJS) {
99 use hermes_parser::ast::node::Node;
100 use hermes_parser::ast::visitor::Visitor;
101
102 /// Counts identifier *expressions* by whether they resolved.
103 struct Counter<'a> {
104 sem: &'a hermes_sema::sem_context::SemContext,
105 resolved: usize,
106 unresolved: usize,
107 }
108 impl<'gc> Visitor<'gc> for Counter<'_> {
109 fn visit_node(&mut self, node: &'gc Node<'gc>) {
110 if let Node::Identifier(id) = node {
111 match self.sem.get_expression_decl(id) {
112 Some(_) => self.resolved += 1,
113 None => self.unresolved += 1,
114 }
115 }
116 node.visit_children(self);
117 }
118 }
119
120 let (resolved_ids, unresolved_ids) =
121 resolved.with_program(|_gc, root, sem| {
122 let mut c = Counter {
123 sem,
124 resolved: 0,
125 unresolved: 0,
126 };
127 c.visit_node(root);
128 (c.resolved, c.unresolved)
129 });
130
131 let sem = resolved.sem_context();
132 println!("functions: {}", sem.functions_len());
133 println!("resolved references: {resolved_ids}");
134 println!("other identifiers: {unresolved_ids}");
135}Sourcepub fn to_sema_dump(&mut self) -> Vec<u8> ⓘ
pub fn to_sema_dump(&mut self) -> Vec<u8> ⓘ
Dump the SemContext and the annotated AST as text, through
crate::dump::sem_dump — the -dump-sema format, byte-for-byte.
(After resolve_for_compile this is exactly what
hermesc -dump-sema prints for the same input; that differential is
this crate’s correctness gate. After the parser path it is the same
format over the differently-resolved tree, which is what the C++
sema-parser-dump tool prints.)
Bytes rather than a String because an identifier in the source may
be an unpaired surrogate, which the dumper writes out as WTF-8 — not
valid UTF-8. For ordinary sources String::from_utf8 succeeds.
Takes &mut self although it only reads, for the reason
with_program documents: dumping locks the
arena, and taking the lock needs exclusive access to the Context.
§Panics
Takes the arena lock, so it panics if another GCLock is live on
this thread — in particular when called from inside
with_program.
Examples found in repository?
30fn main() -> ExitCode {
31 let mut args = std::env::args().skip(1);
32 let mut summary = false;
33 let mut path = None;
34 for arg in args.by_ref() {
35 match arg.as_str() {
36 "--summary" => summary = true,
37 _ => path = Some(arg),
38 }
39 }
40 let Some(path) = path else {
41 eprintln!("usage: resolve_and_dump [--summary] <file.js>");
42 return ExitCode::from(1);
43 };
44
45 let source = match std::fs::read_to_string(&path) {
46 Ok(s) => s,
47 Err(e) => {
48 eprintln!("resolve_and_dump: cannot read '{path}': {e}");
49 return ExitCode::from(1);
50 }
51 };
52
53 // Step 1: parse. `ParseFlags::default()` is plain ECMAScript; set
54 // `parse_flow`, `parse_ts` or `parse_jsx` for the other dialects.
55 let parsed = match parse_named(&source, &path, ParseFlags::default()) {
56 Ok(parsed) => parsed,
57 Err(e) => {
58 for m in e.messages() {
59 eprint!("{m}");
60 }
61 return ExitCode::from(2);
62 }
63 };
64
65 // Step 2: resolve. The compile path, with the standard globals declared —
66 // what `hermesc -dump-sema` does. `hermes_sema::resolve` is the parser
67 // path instead: no ambient declarations and no AST rewrites.
68 let options = CompileOptions::default();
69 let mut resolved = match resolve_for_compile(parsed, &options) {
70 Ok(resolved) => resolved,
71 Err(e) => {
72 for m in e.messages() {
73 eprint!("{m}");
74 }
75 return ExitCode::from(2);
76 }
77 };
78
79 // Warnings, if any: resolution succeeded, so none of these is an error.
80 for d in resolved.diagnostics() {
81 eprintln!("{}:{}:{}: {}", d.file_name, d.line, d.col, d.message);
82 }
83
84 // Step 3: read the result.
85 if summary {
86 print_summary(&mut resolved);
87 } else {
88 // Bytes, not a `String`: an identifier can be an unpaired surrogate,
89 // which the dumper writes as WTF-8.
90 let dump = resolved.to_sema_dump();
91 std::io::stdout().write_all(&dump).expect("write failed");
92 }
93 ExitCode::SUCCESS
94}Sourcepub fn diagnostics(&self) -> &[ResolvedDiagnostic]
pub fn diagnostics(&self) -> &[ResolvedDiagnostic]
The diagnostics recorded so far, in emission order: the parse’s (which were warnings and notes, since the parse succeeded) followed by resolution’s.
For a ResolvedJS that came out of resolve or
resolve_for_compile these are again warnings and notes only;
resolve_for_parser can return one carrying errors — see its doc.
Render one with hermes_support::render::render_diagnostic.
Examples found in repository?
30fn main() -> ExitCode {
31 let mut args = std::env::args().skip(1);
32 let mut summary = false;
33 let mut path = None;
34 for arg in args.by_ref() {
35 match arg.as_str() {
36 "--summary" => summary = true,
37 _ => path = Some(arg),
38 }
39 }
40 let Some(path) = path else {
41 eprintln!("usage: resolve_and_dump [--summary] <file.js>");
42 return ExitCode::from(1);
43 };
44
45 let source = match std::fs::read_to_string(&path) {
46 Ok(s) => s,
47 Err(e) => {
48 eprintln!("resolve_and_dump: cannot read '{path}': {e}");
49 return ExitCode::from(1);
50 }
51 };
52
53 // Step 1: parse. `ParseFlags::default()` is plain ECMAScript; set
54 // `parse_flow`, `parse_ts` or `parse_jsx` for the other dialects.
55 let parsed = match parse_named(&source, &path, ParseFlags::default()) {
56 Ok(parsed) => parsed,
57 Err(e) => {
58 for m in e.messages() {
59 eprint!("{m}");
60 }
61 return ExitCode::from(2);
62 }
63 };
64
65 // Step 2: resolve. The compile path, with the standard globals declared —
66 // what `hermesc -dump-sema` does. `hermes_sema::resolve` is the parser
67 // path instead: no ambient declarations and no AST rewrites.
68 let options = CompileOptions::default();
69 let mut resolved = match resolve_for_compile(parsed, &options) {
70 Ok(resolved) => resolved,
71 Err(e) => {
72 for m in e.messages() {
73 eprint!("{m}");
74 }
75 return ExitCode::from(2);
76 }
77 };
78
79 // Warnings, if any: resolution succeeded, so none of these is an error.
80 for d in resolved.diagnostics() {
81 eprintln!("{}:{}:{}: {}", d.file_name, d.line, d.col, d.message);
82 }
83
84 // Step 3: read the result.
85 if summary {
86 print_summary(&mut resolved);
87 } else {
88 // Bytes, not a `String`: an identifier can be an unpaired surrogate,
89 // which the dumper writes as WTF-8.
90 let dump = resolved.to_sema_dump();
91 std::io::stdout().write_all(&dump).expect("write failed");
92 }
93 ExitCode::SUCCESS
94}Sourcepub fn error_count(&self) -> u32
pub fn error_count(&self) -> u32
How many of the recorded diagnostics are errors.
Zero for a ResolvedJS from resolve or resolve_for_compile.
This is the check the C++ resolveASTForParser callers make instead
of looking at a return value, so it is what
resolve_for_parser’s result must be tested with.
Sourcepub fn source_manager(&self) -> &SourceErrorManager
pub fn source_manager(&self) -> &SourceErrorManager
The source manager owning the parsed buffer (and the libhermes
buffer, if CompileOptions::std_globals was on), for coordinate
lookups and for driving the AST dumper by hand.
Sourcepub fn into_parsed(self) -> ParsedJS
pub fn into_parsed(self) -> ParsedJS
Give back the ParsedJS, now holding the resolved AST, and drop
the SemContext.
This is how the rest of the parser façade’s surface — ESTree JSON
dumping in particular, which is what a resolve-then-serialize consumer
like hermes-parser-wasm does — stays reachable after resolution
without this type mirroring it method for method. The AST keeps every
rewrite the resolver made; only the Decl/scope tables go away.