use oxc_allocator::{Dummy, GetAllocator};
use rustc_hash::FxHashMap;
use crate::{
Context, ParserConfig, ParserImpl,
diagnostics::ParserDiagnostic,
lexer::{Token, trivia_builder::SpeculativeTrivia},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum SpeculativeProduction {
Type,
Assignment { allow_return_type_in_arrow_function: bool },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Effects {
grammar: u64,
module_record: u64,
trivia: u64,
script_errors: usize,
module_errors: usize,
encountered_await_identifier: bool,
potential_await_reparse: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct FailedSubproblemKey {
production: SpeculativeProduction,
token: Token,
lexer_position: (u32, Token),
previous_token_end: u32,
context: Context,
effects: Effects,
trivia: SpeculativeTrivia,
}
pub(crate) struct FailedSubproblem<'a> {
error: ParserDiagnostic<'a>,
token: Token,
lexer_token: Token,
previous_token_end: u32,
trivia: SpeculativeTrivia,
}
#[derive(Default)]
pub(crate) struct FailedSubproblems<'a> {
entries: Option<Box<FxHashMap<FailedSubproblemKey, FailedSubproblem<'a>>>>,
}
impl<'a> FailedSubproblems<'a> {
fn get(&self, key: &FailedSubproblemKey) -> Option<&FailedSubproblem<'a>> {
self.entries.as_deref()?.get(key)
}
fn insert(&mut self, key: FailedSubproblemKey, failed: FailedSubproblem<'a>) {
self.entries.get_or_insert_with(Box::default).insert(key, failed);
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.entries.as_deref().map_or(0, |entries| entries.len())
}
#[cfg(test)]
pub(crate) fn capacity(&self) -> usize {
self.entries.as_deref().map_or(0, |entries| entries.capacity())
}
#[cfg(test)]
pub(crate) fn is_allocated(&self) -> bool {
self.entries.is_some()
}
#[cfg(test)]
pub(crate) fn has_assignment(&self) -> bool {
self.entries.as_deref().is_some_and(|entries| {
entries
.keys()
.any(|key| matches!(key.production, SpeculativeProduction::Assignment { .. }))
})
}
}
impl<'a, C: ParserConfig> ParserImpl<'a, C> {
#[inline]
fn speculative_effects(&self) -> Effects {
Effects {
grammar: self.state.speculation_effects_revision,
module_record: self.module_record_builder.speculation_effects_revision,
trivia: self.lexer.trivia_builder.speculation_effects_revision,
script_errors: self.deferred_script_errors.len(),
module_errors: self.lexer.deferred_module_errors.len(),
encountered_await_identifier: self.state.encountered_await_identifier,
potential_await_reparse: self.state.potential_await_reparse.len(),
}
}
#[inline]
pub(crate) fn memoize_failed_speculative_subproblem<T: Dummy<'a>>(
&mut self,
production: SpeculativeProduction,
parse: impl FnOnce(&mut Self) -> T,
) -> T {
#[cfg(test)]
{
self.state.memo_entry_calls += 1;
}
if self.state.type_argument_speculation_depth == 0 || self.fatal_error.is_some() {
return parse(self);
}
#[cfg(test)]
{
self.state.subproblem_visits += 1;
if self.state.disable_failed_subproblems {
return parse(self);
}
}
if !self.state.reuse_failed_type_argument_subproblems {
return parse(self);
}
let key = FailedSubproblemKey {
production,
token: self.token,
lexer_position: self.lexer.speculative_position(),
previous_token_end: self.prev_token_end,
context: self.ctx,
effects: self.speculative_effects(),
trivia: self.lexer.trivia_builder.speculative_state(),
};
if let Some(failed) = self.state.failed_speculative_subproblems.get(&key) {
let error = failed.error.clone();
let lexer_token = failed.lexer_token;
self.token = failed.token;
self.prev_token_end = failed.previous_token_end;
self.lexer.trivia_builder.restore_speculative_state(failed.trivia);
self.set_fatal_error(error);
self.lexer.restore_failed_speculative_token(lexer_token);
#[cfg(test)]
{
self.state.subproblem_reuses += 1;
}
return Dummy::dummy(self.allocator());
}
let value = parse(self);
if key.effects == self.speculative_effects()
&& key.context == self.ctx
&& let Some(fatal) = &self.fatal_error
{
self.state.failed_speculative_subproblems.insert(
key,
FailedSubproblem {
error: fatal.error.clone(),
token: self.token,
lexer_token: self.lexer.speculative_position().1,
previous_token_end: self.prev_token_end,
trivia: self.lexer.trivia_builder.speculative_state(),
},
);
}
value
}
}
#[cfg(test)]
mod tests;