perf(semantic): reduce visit parent nodes in resolve_reference_usages (#2419)

This commit is contained in:
Dunqing 2024-02-16 20:52:35 +08:00 committed by GitHub
parent cc2ddbee77
commit 8110288bf4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 56 additions and 67 deletions

View file

@ -2,7 +2,6 @@
use std::{cell::RefCell, path::PathBuf, rc::Rc, sync::Arc}; use std::{cell::RefCell, path::PathBuf, rc::Rc, sync::Arc};
use itertools::Itertools;
#[allow(clippy::wildcard_imports)] #[allow(clippy::wildcard_imports)]
use oxc_ast::{ast::*, AstKind, Trivias, TriviasMap, Visit}; use oxc_ast::{ast::*, AstKind, Trivias, TriviasMap, Visit};
use oxc_diagnostics::Error; use oxc_diagnostics::Error;
@ -62,6 +61,7 @@ pub struct SemanticBuilder<'a> {
pub namespace_stack: Vec<SymbolId>, pub namespace_stack: Vec<SymbolId>,
/// If true, the current node is in the type definition /// If true, the current node is in the type definition
in_type_definition: bool, in_type_definition: bool,
current_reference_flag: ReferenceFlag,
// builders // builders
pub nodes: AstNodes<'a>, pub nodes: AstNodes<'a>,
@ -103,6 +103,7 @@ impl<'a> SemanticBuilder<'a> {
current_node_flags: NodeFlags::empty(), current_node_flags: NodeFlags::empty(),
current_symbol_flags: SymbolFlags::empty(), current_symbol_flags: SymbolFlags::empty(),
in_type_definition: false, in_type_definition: false,
current_reference_flag: ReferenceFlag::empty(),
current_scope_id, current_scope_id,
function_stack: vec![], function_stack: vec![],
namespace_stack: vec![], namespace_stack: vec![],
@ -1723,6 +1724,22 @@ impl<'a> SemanticBuilder<'a> {
AstKind::IdentifierReference(ident) => { AstKind::IdentifierReference(ident) => {
self.reference_identifier(ident); self.reference_identifier(ident);
} }
AstKind::UpdateExpression(_) => {
if self.is_not_expression_statement_parent() {
self.current_reference_flag |= ReferenceFlag::Read;
}
self.current_reference_flag |= ReferenceFlag::Write;
}
AstKind::AssignmentExpression(expr) => {
if self.is_not_expression_statement_parent()
|| expr.operator != AssignmentOperator::Assign
{
self.current_reference_flag |= ReferenceFlag::Read;
}
}
AstKind::AssignmentTarget(_) => {
self.current_reference_flag |= ReferenceFlag::Write;
}
AstKind::JSXElementName(elem) => { AstKind::JSXElementName(elem) => {
self.reference_jsx_element_name(elem); self.reference_jsx_element_name(elem);
} }
@ -1774,6 +1791,20 @@ impl<'a> SemanticBuilder<'a> {
| AstKind::TSTypeAnnotation(_) => { | AstKind::TSTypeAnnotation(_) => {
self.in_type_definition = false; self.in_type_definition = false;
} }
AstKind::UpdateExpression(_) => {
if self.is_not_expression_statement_parent() {
self.current_reference_flag -= ReferenceFlag::Read;
}
self.current_reference_flag -= ReferenceFlag::Write;
}
AstKind::AssignmentExpression(expr) => {
if self.is_not_expression_statement_parent()
|| expr.operator != AssignmentOperator::Assign
{
self.current_reference_flag -= ReferenceFlag::Read;
}
}
AstKind::AssignmentTarget(_) => self.current_reference_flag -= ReferenceFlag::Write,
_ => {} _ => {}
} }
} }
@ -1802,71 +1833,17 @@ impl<'a> SemanticBuilder<'a> {
/// Resolve reference flags for the current ast node. /// Resolve reference flags for the current ast node.
fn resolve_reference_usages(&self) -> ReferenceFlag { fn resolve_reference_usages(&self) -> ReferenceFlag {
if self.in_type_definition { if self.in_type_definition {
return ReferenceFlag::Type; ReferenceFlag::Type
} } else if self.current_reference_flag.is_write()
&& !matches!(
let mut flags = ReferenceFlag::None; self.nodes.parent_kind(self.current_node_id),
Some(AstKind::MemberExpression(_))
if self.nodes.parent_id(self.current_node_id).is_none() { )
return ReferenceFlag::Read;
}
// This func should only get called when an IdentifierReference is
// reached
debug_assert!(matches!(
self.nodes.get_node(self.current_node_id).kind(),
AstKind::IdentifierReference(_)
));
for (curr, parent) in self
.nodes
.iter_parents(self.current_node_id)
.tuple_windows::<(&AstNode<'a>, &AstNode<'a>)>()
{ {
match (curr.kind(), parent.kind()) { self.current_reference_flag
// lhs of assignment expression } else {
(AstKind::SimpleAssignmentTarget(_), AstKind::AssignmentExpression(_)) => { ReferenceFlag::Read
debug_assert!(!flags.is_read());
flags = ReferenceFlag::write();
// a lhs expr will not propagate upwards into a rhs
// expression, sow e can safely break
break;
}
(AstKind::AssignmentTarget(_), AstKind::AssignmentExpression(expr)) => {
flags |= if expr.operator == AssignmentOperator::Assign {
ReferenceFlag::write()
} else {
ReferenceFlag::read_write()
};
break;
}
(_, AstKind::SimpleAssignmentTarget(_) | AstKind::AssignmentTarget(_)) => {
flags |= ReferenceFlag::write();
// continue up tree
}
(_, AstKind::UpdateExpression(_)) => {
flags |= ReferenceFlag::Write;
// continue up tree
}
(
AstKind::AssignmentTarget(_),
AstKind::ForInStatement(_) | AstKind::ForOfStatement(_),
) => {
break;
}
(_, AstKind::ParenthesizedExpression(_)) => {
// continue up tree
}
_ => {
flags |= ReferenceFlag::Read;
break;
}
}
} }
debug_assert!(flags != ReferenceFlag::None);
flags
} }
fn reference_jsx_element_name(&mut self, elem: &JSXElementName) { fn reference_jsx_element_name(&mut self, elem: &JSXElementName) {
@ -1893,4 +1870,15 @@ impl<'a> SemanticBuilder<'a> {
} }
} }
} }
fn is_not_expression_statement_parent(&self) -> bool {
for node in self.nodes.iter_parents(self.current_node_id).skip(1) {
return match node.kind() {
AstKind::ParenthesizedExpression(_) => continue,
AstKind::ExpressionStatement(_) => false,
_ => true,
};
}
false
}
} }

View file

@ -254,6 +254,7 @@ mod tests {
// simple cases // simple cases
(SourceType::default(), "let a = 1; a = 2", ReferenceFlag::write()), (SourceType::default(), "let a = 1; a = 2", ReferenceFlag::write()),
(SourceType::default(), "let a = 1, b; b = a", ReferenceFlag::read()), (SourceType::default(), "let a = 1, b; b = a", ReferenceFlag::read()),
(SourceType::default(), "let a = 1, b; b[a]", ReferenceFlag::read()),
(SourceType::default(), "let a = 1, b = 1, c; c = a + b", ReferenceFlag::read()), (SourceType::default(), "let a = 1, b = 1, c; c = a + b", ReferenceFlag::read()),
(SourceType::default(), "function a() { return }; a()", ReferenceFlag::read()), (SourceType::default(), "function a() { return }; a()", ReferenceFlag::read()),
(SourceType::default(), "class a {}; new a()", ReferenceFlag::read()), (SourceType::default(), "class a {}; new a()", ReferenceFlag::read()),
@ -285,8 +286,8 @@ mod tests {
(SourceType::default(), "let a = 1, b; b = a += 5", ReferenceFlag::read_write()), (SourceType::default(), "let a = 1, b; b = a += 5", ReferenceFlag::read_write()),
(SourceType::default(), "let a = 1; a += 5", ReferenceFlag::read_write()), (SourceType::default(), "let a = 1; a += 5", ReferenceFlag::read_write()),
// note: we consider a to be written, and the read of `1` propagates upwards // note: we consider a to be written, and the read of `1` propagates upwards
(SourceType::default(), "let a, b; b = a = 1", ReferenceFlag::write()), (SourceType::default(), "let a, b; b = a = 1", ReferenceFlag::read_write()),
(SourceType::default(), "let a, b; b = (a = 1)", ReferenceFlag::write()), (SourceType::default(), "let a, b; b = (a = 1)", ReferenceFlag::read_write()),
(SourceType::default(), "let a, b, c; b = c = a", ReferenceFlag::read()), (SourceType::default(), "let a, b, c; b = c = a", ReferenceFlag::read()),
// sequences return last value in sequence // sequences return last value in sequence
(SourceType::default(), "let a, b; b = (0, a++)", ReferenceFlag::read_write()), (SourceType::default(), "let a, b; b = (0, a++)", ReferenceFlag::read_write()),
@ -324,7 +325,7 @@ mod tests {
( (
SourceType::default(), SourceType::default(),
"let a; function foo(a) { return a }; foo(a = 1)", "let a; function foo(a) { return a }; foo(a = 1)",
ReferenceFlag::write(), ReferenceFlag::read_write(),
), ),
// typescript // typescript
(typescript, "let a: number = 1; (a as any) = true", ReferenceFlag::write()), (typescript, "let a: number = 1; (a as any) = true", ReferenceFlag::write()),