Skip to main content

ResolvedJS

Struct ResolvedJS 

Source
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

Source

pub fn with_program<R, F>(&mut self, f: F) -> R
where F: for<'gc> FnOnce(&'gc GCLock<'static, '_>, &'gc Node<'gc>, &SemContext) -> 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 'gcGCLock<'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?
examples/resolve_and_dump.rs (lines 121-129)
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
Hide additional examples
examples/print_bindings.rs (lines 166-174)
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}
Source

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?
examples/resolve_and_dump.rs (line 131)
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}
Source

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?
examples/resolve_and_dump.rs (line 90)
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}
Source

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?
examples/resolve_and_dump.rs (line 80)
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}
Source

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.

Source

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.

Source

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.

Trait Implementations§

Source§

impl Debug for ResolvedJS

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Summarizes rather than printing the AST or the tables, which can be huge. (Hand-written because neither ParsedJS nor SemContext derives Debug.)

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.