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
This commit is contained in:
yangchenye 2023-04-09 21:20:15 -05:00 committed by GitHub
parent 84d252df01
commit 35b19e7edf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 169 additions and 44 deletions

View file

@ -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)]

View file

@ -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 }

View file

@ -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));
}
}
}
}
}

View file

@ -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();

View file

@ -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<Symbol>,
/// Stores all the resolved references indexed by `ResolvedReferenceId`
resolved_references: Vec<ResolvedReference>,
// BTreeMap is empirically a lot faster than FxHashMap for our insertion,
resolved_references_index: BTreeMap<Span, ResolvedReferenceId>,
}
impl Index<SymbolId> for SymbolTableBuilder {
type Output = Symbol;
fn index(&self, index: SymbolId) -> &Self::Output {
&self.symbols[index.index0()]
}
}
impl IndexMut<SymbolId> 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<Reference>, 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)
}
}

View file

@ -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,

View file

@ -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<Symbol>,
/// Stores all the resolved references indexed by `ResolvedReferenceId`
resolved_references: Vec<ResolvedReference>,
resolved_references_index: BTreeMap<Span, ResolvedReferenceId>,
}
impl Index<SymbolId> for SymbolTable {
@ -57,6 +59,15 @@ impl Deref for SymbolTable {
}
impl SymbolTable {
#[must_use]
pub fn new(
symbols: Vec<Symbol>,
resolved_references: Vec<ResolvedReference>,
resolved_references_index: BTreeMap<Span, ResolvedReferenceId>,
) -> Self {
Self { symbols, resolved_references, resolved_references_index }
}
#[must_use]
pub fn symbols(&self) -> &Vec<Symbol> {
&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<ResolvedReference> {
&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<Reference>, 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()])
}
}