feat(xatu): add lexical scope graph
This commit is contained in:
@@ -11,6 +11,7 @@ rust_library(
|
||||
"rules/manual_inherit.rs",
|
||||
"rules/manual_inherit_from.rs",
|
||||
"rules/unquoted_uri.rs",
|
||||
"scope_graph.rs",
|
||||
"xatu.rs",
|
||||
],
|
||||
deps = [
|
||||
@@ -26,6 +27,15 @@ rust_test(
|
||||
deps = [":xatu_lib"],
|
||||
)
|
||||
|
||||
rust_test(
|
||||
name = "scope_graph_test",
|
||||
srcs = ["scope_graph_test.rs"],
|
||||
deps = [
|
||||
":xatu_lib",
|
||||
"//third_party/crates:googletest",
|
||||
],
|
||||
)
|
||||
|
||||
rust_test(
|
||||
name = "xatu_test",
|
||||
srcs = ["xatu_test.rs"],
|
||||
|
||||
172
src/xatu/scope_graph.rs
Normal file
172
src/xatu/scope_graph.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
#[derive(Debug)]
|
||||
pub struct ScopeGraph {
|
||||
scopes: Vec<Scope>,
|
||||
declarations: Vec<Declaration>,
|
||||
references: Vec<Reference>,
|
||||
}
|
||||
|
||||
impl Default for ScopeGraph {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ScopeGraph {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
scopes: vec![Scope::new(None)],
|
||||
declarations: Vec::new(),
|
||||
references: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_scope(&self) -> ScopeId {
|
||||
ScopeId(0)
|
||||
}
|
||||
|
||||
pub fn add_scope(&mut self, parent: ScopeId) -> ScopeId {
|
||||
self.scope(parent);
|
||||
let scope_id = ScopeId(self.scopes.len());
|
||||
|
||||
self.scopes.push(Scope::new(Some(parent)));
|
||||
|
||||
scope_id
|
||||
}
|
||||
|
||||
pub fn add_declaration(&mut self, scope: ScopeId, name: impl Into<String>) -> DeclarationId {
|
||||
let declaration_id = DeclarationId(self.declarations.len());
|
||||
self.declarations.push(Declaration {
|
||||
scope,
|
||||
name: name.into(),
|
||||
});
|
||||
self.scope_mut(scope).declarations.push(declaration_id);
|
||||
|
||||
declaration_id
|
||||
}
|
||||
|
||||
pub fn add_reference(&mut self, scope: ScopeId, name: impl Into<String>) -> ReferenceId {
|
||||
let reference_id = ReferenceId(self.references.len());
|
||||
self.references.push(Reference {
|
||||
scope,
|
||||
name: name.into(),
|
||||
});
|
||||
self.scope_mut(scope).references.push(reference_id);
|
||||
|
||||
reference_id
|
||||
}
|
||||
|
||||
pub fn declaration_scope(&self, declaration: DeclarationId) -> ScopeId {
|
||||
self.declaration(declaration).scope
|
||||
}
|
||||
|
||||
pub fn reference_scope(&self, reference: ReferenceId) -> ScopeId {
|
||||
self.reference(reference).scope
|
||||
}
|
||||
|
||||
pub fn resolve(&self, reference: ReferenceId) -> Vec<Resolution> {
|
||||
let reference = self.reference(reference);
|
||||
let mut scope_id = reference.scope;
|
||||
let mut steps = Vec::new();
|
||||
|
||||
loop {
|
||||
let scope = self.scope(scope_id);
|
||||
let declarations =
|
||||
scope.declarations.iter().copied().filter(|declaration_id| {
|
||||
self.declaration(*declaration_id).name == reference.name
|
||||
});
|
||||
|
||||
let resolutions = declarations
|
||||
.map(|declaration| {
|
||||
let mut path = steps.clone();
|
||||
path.push(PathStep::Declaration(declaration));
|
||||
Resolution {
|
||||
declaration,
|
||||
path: ResolutionPath { steps: path },
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !resolutions.is_empty() {
|
||||
return resolutions;
|
||||
}
|
||||
|
||||
let Some(parent) = scope.parent else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
steps.push(PathStep::Parent);
|
||||
scope_id = parent;
|
||||
}
|
||||
}
|
||||
|
||||
fn scope(&self, scope: ScopeId) -> &Scope {
|
||||
&self.scopes[scope.0]
|
||||
}
|
||||
|
||||
fn scope_mut(&mut self, scope: ScopeId) -> &mut Scope {
|
||||
&mut self.scopes[scope.0]
|
||||
}
|
||||
|
||||
fn declaration(&self, declaration: DeclarationId) -> &Declaration {
|
||||
&self.declarations[declaration.0]
|
||||
}
|
||||
|
||||
fn reference(&self, reference: ReferenceId) -> &Reference {
|
||||
&self.references[reference.0]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ScopeId(usize);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct DeclarationId(usize);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ReferenceId(usize);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Resolution {
|
||||
pub declaration: DeclarationId,
|
||||
pub path: ResolutionPath,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ResolutionPath {
|
||||
pub steps: Vec<PathStep>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PathStep {
|
||||
Parent,
|
||||
Declaration(DeclarationId),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Scope {
|
||||
parent: Option<ScopeId>,
|
||||
declarations: Vec<DeclarationId>,
|
||||
references: Vec<ReferenceId>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
fn new(parent: Option<ScopeId>) -> Self {
|
||||
Self {
|
||||
parent,
|
||||
declarations: Vec::new(),
|
||||
references: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Declaration {
|
||||
scope: ScopeId,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Reference {
|
||||
scope: ScopeId,
|
||||
name: String,
|
||||
}
|
||||
76
src/xatu/scope_graph_test.rs
Normal file
76
src/xatu/scope_graph_test.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use googletest::matchers::eq;
|
||||
use googletest::{expect_that, gtest};
|
||||
use xatu::{PathStep, Resolution, ResolutionPath, ScopeGraph};
|
||||
|
||||
#[gtest]
|
||||
fn resolves_a_local_declaration() {
|
||||
let mut graph = ScopeGraph::new();
|
||||
let scope = graph.root_scope();
|
||||
let declaration = graph.add_declaration(scope, "value");
|
||||
let reference = graph.add_reference(scope, "value");
|
||||
|
||||
let expected = vec![resolution(declaration, vec![PathStep::Declaration(declaration)])];
|
||||
expect_that!(graph.resolve(reference), eq(&expected));
|
||||
}
|
||||
|
||||
#[gtest]
|
||||
fn resolves_a_parent_declaration() {
|
||||
let mut graph = ScopeGraph::new();
|
||||
let parent = graph.root_scope();
|
||||
let declaration = graph.add_declaration(parent, "value");
|
||||
let child = graph.add_scope(parent);
|
||||
let reference = graph.add_reference(child, "value");
|
||||
|
||||
let expected = vec![resolution(
|
||||
declaration,
|
||||
vec![PathStep::Parent, PathStep::Declaration(declaration)],
|
||||
)];
|
||||
expect_that!(graph.resolve(reference), eq(&expected));
|
||||
}
|
||||
|
||||
#[gtest]
|
||||
fn local_declarations_shadow_parent_declarations() {
|
||||
let mut graph = ScopeGraph::new();
|
||||
let parent = graph.root_scope();
|
||||
graph.add_declaration(parent, "value");
|
||||
let child = graph.add_scope(parent);
|
||||
let declaration = graph.add_declaration(child, "value");
|
||||
let reference = graph.add_reference(child, "value");
|
||||
|
||||
let expected = vec![resolution(declaration, vec![PathStep::Declaration(declaration)])];
|
||||
expect_that!(graph.resolve(reference), eq(&expected));
|
||||
}
|
||||
|
||||
#[gtest]
|
||||
fn preserves_ambiguous_local_declarations() {
|
||||
let mut graph = ScopeGraph::new();
|
||||
let scope = graph.root_scope();
|
||||
let first = graph.add_declaration(scope, "value");
|
||||
let second = graph.add_declaration(scope, "value");
|
||||
let reference = graph.add_reference(scope, "value");
|
||||
|
||||
let expected = vec![
|
||||
resolution(first, vec![PathStep::Declaration(first)]),
|
||||
resolution(second, vec![PathStep::Declaration(second)]),
|
||||
];
|
||||
expect_that!(graph.resolve(reference), eq(&expected));
|
||||
}
|
||||
|
||||
#[gtest]
|
||||
fn leaves_unresolved_references_without_resolutions() {
|
||||
let mut graph = ScopeGraph::new();
|
||||
let reference = graph.add_reference(graph.root_scope(), "value");
|
||||
|
||||
let expected = Vec::<Resolution>::new();
|
||||
expect_that!(graph.resolve(reference), eq(&expected));
|
||||
}
|
||||
|
||||
fn resolution(
|
||||
declaration: xatu::DeclarationId,
|
||||
steps: Vec<PathStep>,
|
||||
) -> Resolution {
|
||||
Resolution {
|
||||
declaration,
|
||||
path: ResolutionPath { steps },
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,13 @@ mod ast;
|
||||
mod file_store;
|
||||
mod parser;
|
||||
mod rules;
|
||||
mod scope_graph;
|
||||
|
||||
pub use file_store::{FileId, FileStore, SourceFile};
|
||||
pub use rules::{Diagnostic, Finding};
|
||||
pub use scope_graph::{
|
||||
DeclarationId, PathStep, ReferenceId, Resolution, ResolutionPath, ScopeGraph, ScopeId,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Report {
|
||||
|
||||
Reference in New Issue
Block a user