From 35b19e7edfef5a090755d65f914ef7f2aead0651 Mon Sep 17 00:00:00 2001 From: yangchenye Date: Sun, 9 Apr 2023 21:20:15 -0500 Subject: [PATCH] feat(semantic): Add index mapping from span to reference id (#270) * feat(semantic): Add index mapping from span to reference id * Switch to BTreeMap for index --- crates/oxc_linter/src/context.rs | 5 +- crates/oxc_semantic/src/builder.rs | 9 +-- crates/oxc_semantic/src/lib.rs | 63 ++++++++++++++++++- crates/oxc_semantic/src/scope/builder.rs | 4 +- crates/oxc_semantic/src/symbol/builder.rs | 73 +++++++++++++++++++++++ crates/oxc_semantic/src/symbol/mod.rs | 2 + crates/oxc_semantic/src/symbol/table.rs | 57 +++++++----------- 7 files changed, 169 insertions(+), 44 deletions(-) create mode 100644 crates/oxc_semantic/src/symbol/builder.rs diff --git a/crates/oxc_linter/src/context.rs b/crates/oxc_linter/src/context.rs index ebcac4c7c..160ba79ff 100644 --- a/crates/oxc_linter/src/context.rs +++ b/crates/oxc_linter/src/context.rs @@ -143,9 +143,8 @@ impl<'a> LintContext<'a> { /* Symbols */ - #[allow(clippy::unused_self)] - pub fn is_reference_to_global_variable(&self, _ident: &IdentifierReference) -> bool { - true + pub fn is_reference_to_global_variable(&self, ident: &IdentifierReference) -> bool { + self.semantic().is_reference_to_global_variables(ident) } #[allow(clippy::unused_self)] diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index 99f5ea8a0..08478f2fd 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -16,7 +16,7 @@ use crate::{ module_record::ModuleRecordBuilder, node::{AstNodeId, AstNodes, NodeFlags, SemanticNode}, scope::{ScopeBuilder, ScopeId}, - symbol::{Reference, ReferenceFlag, SymbolFlags, SymbolId, SymbolTable}, + symbol::{Reference, ReferenceFlag, SymbolFlags, SymbolId, SymbolTableBuilder}, Semantic, }; @@ -38,7 +38,7 @@ pub struct SemanticBuilder<'a> { // builders pub nodes: AstNodes<'a>, pub scope: ScopeBuilder, - pub symbols: SymbolTable, + pub symbols: SymbolTableBuilder, with_module_record_builder: bool, module_record_builder: ModuleRecordBuilder, @@ -67,7 +67,7 @@ impl<'a> SemanticBuilder<'a> { current_symbol_flags: SymbolFlags::empty(), nodes, scope, - symbols: SymbolTable::default(), + symbols: SymbolTableBuilder::default(), with_module_record_builder: false, module_record_builder: ModuleRecordBuilder::default(), } @@ -84,6 +84,7 @@ impl<'a> SemanticBuilder<'a> { // First AST pass self.visit_program(program); + let symbols = self.symbols.build(); // Second partial AST pass on top level import / export statements let module_record = if self.with_module_record_builder { self.module_record_builder.build(program) @@ -97,7 +98,7 @@ impl<'a> SemanticBuilder<'a> { trivias: self.trivias, nodes: self.nodes, scopes: self.scope.scopes, - symbols: self.symbols, + symbols, module_record, }; SemanticBuilderReturn { semantic, errors: self.errors } diff --git a/crates/oxc_semantic/src/lib.rs b/crates/oxc_semantic/src/lib.rs index 2517102a7..e411c7a38 100644 --- a/crates/oxc_semantic/src/lib.rs +++ b/crates/oxc_semantic/src/lib.rs @@ -13,8 +13,12 @@ use std::rc::Rc; pub use builder::SemanticBuilder; use node::AstNodeId; pub use node::{AstNode, AstNodes, SemanticNode}; -use oxc_ast::{module_record::ModuleRecord, AstKind, SourceType, Trivias}; +use oxc_ast::{ + ast::IdentifierReference, module_record::ModuleRecord, AstKind, SourceType, Trivias, +}; +use scope::ScopeId; pub use scope::{Scope, ScopeFlags, ScopeTree}; +use symbol::SymbolId; pub use symbol::{Reference, ResolvedReference, Symbol, SymbolFlags, SymbolTable}; pub struct Semantic<'a> { @@ -76,4 +80,61 @@ impl<'a> Semantic<'a> { let scope = &self.scopes()[reference_node.scope_id()]; scope.unresolved_references.contains_key(&id.name) } + + #[must_use] + pub fn symbol_scope(&self, symbol_id: SymbolId) -> ScopeId { + let symbol = &self.symbols[symbol_id]; + let declaration = symbol.declaration(); + self.nodes[declaration].scope_id() + } + + #[must_use] + pub fn is_reference_to_global_variables(&self, id: &IdentifierReference) -> bool { + // unresolved references are treated as reference to global. + self.symbols.get_resolved_reference_for_id(id).map_or(true, |reference_id| { + let referred_symbol = reference_id.resolved_symbol_id; + let symbol_scope = self.symbol_scope(referred_symbol); + // Symbol declared in top level + symbol_scope == self.scopes().root_scope_id() + }) + } +} + +#[cfg(test)] +mod tests { + use oxc_allocator::Allocator; + use oxc_ast::{AstKind, SourceType}; + + use crate::SemanticBuilder; + + #[test] + fn test_is_global() { + let source = " + var a = 0; + function foo() { + a += 1; + } + + var b = a + 2; + + console.log(b); + "; + let allocator = Allocator::default(); + let source_type = SourceType::default(); + let parse = + oxc_parser::Parser::new(&allocator, source, oxc_ast::SourceType::default()).parse(); + assert!(parse.errors.is_empty()); + let program = allocator.alloc(parse.program); + + { + let semantic = SemanticBuilder::new(source, source_type, &parse.trivias).build(program); + assert!(semantic.errors.is_empty()); + let semantic = semantic.semantic; + for node in semantic.nodes().iter() { + if let AstKind::IdentifierReference(id) = node.get().kind() { + assert!(semantic.is_reference_to_global_variables(id)); + } + } + } + } } diff --git a/crates/oxc_semantic/src/scope/builder.rs b/crates/oxc_semantic/src/scope/builder.rs index 5afc39d65..fa2d0f3e1 100644 --- a/crates/oxc_semantic/src/scope/builder.rs +++ b/crates/oxc_semantic/src/scope/builder.rs @@ -2,7 +2,7 @@ use oxc_ast::{ast::ClassType, AstKind, Atom, SourceType}; use rustc_hash::FxHashMap; use super::{Scope, ScopeFlags, ScopeId, ScopeTree}; -use crate::{symbol::Reference, SymbolTable}; +use crate::symbol::{Reference, SymbolTableBuilder}; #[derive(Debug)] pub struct ScopeBuilder { @@ -58,7 +58,7 @@ impl ScopeBuilder { } } - pub fn resolve_reference(&mut self, symbol_table: &mut SymbolTable) { + pub fn resolve_reference(&mut self, symbol_table: &mut SymbolTableBuilder) { // At the initial stage, all references are unresolved. let all_references = { let current_scope = self.current_scope_mut(); diff --git a/crates/oxc_semantic/src/symbol/builder.rs b/crates/oxc_semantic/src/symbol/builder.rs new file mode 100644 index 000000000..aeb468d8c --- /dev/null +++ b/crates/oxc_semantic/src/symbol/builder.rs @@ -0,0 +1,73 @@ +use std::{ + collections::BTreeMap, + ops::{Index, IndexMut}, +}; + +use oxc_ast::{Atom, Span}; + +use super::{reference::ResolvedReferenceId, SymbolId}; +use crate::{node::AstNodeId, Reference, ResolvedReference, Symbol, SymbolFlags, SymbolTable}; + +#[derive(Debug, Default)] +pub struct SymbolTableBuilder { + /// Stores all the `Symbols` indexed by `SymbolId` + symbols: Vec, + /// Stores all the resolved references indexed by `ResolvedReferenceId` + resolved_references: Vec, + // BTreeMap is empirically a lot faster than FxHashMap for our insertion, + resolved_references_index: BTreeMap, +} + +impl Index for SymbolTableBuilder { + type Output = Symbol; + + fn index(&self, index: SymbolId) -> &Self::Output { + &self.symbols[index.index0()] + } +} + +impl IndexMut for SymbolTableBuilder { + fn index_mut(&mut self, index: SymbolId) -> &mut Self::Output { + &mut self.symbols[index.index0()] + } +} + +impl SymbolTableBuilder { + #[must_use] + pub fn create( + &mut self, + declaration: AstNodeId, + name: Atom, + span: Span, + flags: SymbolFlags, + ) -> SymbolId { + let symbol_id = SymbolId::new(self.symbols.len() + 1); + let symbol = Symbol::new(symbol_id, declaration, name, span, flags); + self.symbols.push(symbol); + symbol_id + } + + /// Resolve all `references` to `symbol_id` + pub fn resolve_reference(&mut self, references: Vec, symbol_id: SymbolId) { + let additional_len = references.len(); + let symbol = &mut self.symbols[symbol_id]; + + self.resolved_references.reserve(additional_len); + symbol.references.reserve(additional_len); + + for reference in references { + let resolved_reference_id = + ResolvedReferenceId::new(self.resolved_references.len() + 1); + self.resolved_references_index.insert(reference.span, resolved_reference_id); + + let resolved_reference = reference.resolve_to(symbol_id); + self.resolved_references.push(resolved_reference); + // explicitly push to vector here in correspondence to the previous reserve call + symbol.references.push(resolved_reference_id); + } + } + + pub fn build(self) -> SymbolTable { + SymbolTable::new(self.symbols, self.resolved_references, self.resolved_references_index) + } +} diff --git a/crates/oxc_semantic/src/symbol/mod.rs b/crates/oxc_semantic/src/symbol/mod.rs index de28b3daa..08658eb30 100644 --- a/crates/oxc_semantic/src/symbol/mod.rs +++ b/crates/oxc_semantic/src/symbol/mod.rs @@ -1,6 +1,7 @@ //! Symbol and Symbol Table for tracking of semantics of variables #![allow(non_upper_case_globals)] +mod builder; mod id; mod reference; mod table; @@ -10,6 +11,7 @@ use oxc_ast::{Atom, Span}; use self::reference::ResolvedReferenceId; pub use self::{ + builder::SymbolTableBuilder, id::SymbolId, reference::{Reference, ReferenceFlag, ResolvedReference}, table::SymbolTable, diff --git a/crates/oxc_semantic/src/symbol/table.rs b/crates/oxc_semantic/src/symbol/table.rs index 2624d5249..170db594a 100644 --- a/crates/oxc_semantic/src/symbol/table.rs +++ b/crates/oxc_semantic/src/symbol/table.rs @@ -1,11 +1,12 @@ +use std::collections::BTreeMap; use std::ops::{Deref, Index, IndexMut}; -use oxc_ast::{Atom, Span}; +use oxc_ast::ast::IdentifierReference; +use oxc_ast::Span; use super::reference::ResolvedReferenceId; -use super::{Symbol, SymbolFlags, SymbolId}; -use crate::node::AstNodeId; -use crate::{Reference, ResolvedReference}; +use super::{Symbol, SymbolId}; +use crate::ResolvedReference; /// `SymbolTable` is a storage of all the symbols (related to `BindingIdentifiers`) /// and references (related to `IdentifierReferences`) of the program. It supports two @@ -18,6 +19,7 @@ pub struct SymbolTable { symbols: Vec, /// Stores all the resolved references indexed by `ResolvedReferenceId` resolved_references: Vec, + resolved_references_index: BTreeMap, } impl Index for SymbolTable { @@ -57,6 +59,15 @@ impl Deref for SymbolTable { } impl SymbolTable { + #[must_use] + pub fn new( + symbols: Vec, + resolved_references: Vec, + resolved_references_index: BTreeMap, + ) -> Self { + Self { symbols, resolved_references, resolved_references_index } + } + #[must_use] pub fn symbols(&self) -> &Vec { &self.symbols @@ -67,20 +78,6 @@ impl SymbolTable { self.symbols.get(id.index0()) } - #[must_use] - pub(crate) fn create( - &mut self, - declaration: AstNodeId, - name: Atom, - span: Span, - flags: SymbolFlags, - ) -> SymbolId { - let symbol_id = SymbolId::new(self.symbols.len() + 1); - let symbol = Symbol::new(symbol_id, declaration, name, span, flags); - self.symbols.push(symbol); - symbol_id - } - #[must_use] pub fn resolved_references(&self) -> &Vec { &self.resolved_references @@ -91,21 +88,13 @@ impl SymbolTable { self.resolved_references.get(id.index0()) } - /// Resolve all `references` to `symbol_id` - pub(crate) fn resolve_reference(&mut self, references: Vec, symbol_id: SymbolId) { - let additional_len = references.len(); - let symbol = &mut self.symbols[symbol_id]; - - self.resolved_references.reserve(additional_len); - symbol.references.reserve(additional_len); - - for reference in references { - let resolved_reference_id = - ResolvedReferenceId::new(self.resolved_references.len() + 1); - let resolved_reference = reference.resolve_to(symbol_id); - self.resolved_references.push(resolved_reference); - // explicitly push to vector here in correspondence to the previous reserve call - symbol.references.push(resolved_reference_id); - } + #[must_use] + pub fn get_resolved_reference_for_id( + &self, + id: &IdentifierReference, + ) -> Option<&ResolvedReference> { + self.resolved_references_index + .get(&id.span) + .map(|ref_id| &self.resolved_references[ref_id.index0()]) } }