Skip to content

Commit

Permalink
Bin/Check+Analyser: Start laying out foundations of a static analyser
Browse files Browse the repository at this point in the history
  • Loading branch information
ryangjchandler committed Jan 12, 2025
1 parent 3616c5d commit a6ee6ad
Show file tree
Hide file tree
Showing 19 changed files with 456 additions and 26 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ debug = true
anyhow = "1.0.95"
ariadne = { version = "0.5.0", features = ["auto-color"] }
clap = { version = "4.5.23", features = ["derive", "wrap_help"] }
codespan-reporting = "0.11.1"
colored = "2.2.0"
homedir = "0.3.4"
indicatif = "0.17.9"
pxp-analyser = { version = "0.1.0", path = "crates/analyser" }
pxp-bytestring = { version = "0.1.0", path = "crates/bytestring" }
pxp-diagnostics = { version = "0.1.0", path = "crates/diagnostics" }
pxp-index = { version = "0.1.0", path = "crates/index" }
pxp-inference = { version = "0.1.0", path = "crates/inference" }
pxp-lexer = { version = "0.1.0", path = "crates/lexer" }
pxp-parser = { version = "0.1.0", path = "crates/parser" }
pxp-span = { version = "0.1.0", path = "crates/span" }
Expand Down
5 changes: 5 additions & 0 deletions crates/analyser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ rust-version.workspace = true
edition.workspace = true

[dependencies]
pxp-ast = { version = "0.1.0", path = "../ast" }
pxp-diagnostics = { version = "0.1.0", path = "../diagnostics" }
pxp-inference = { version = "0.1.0", path = "../inference" }
pxp-span = { version = "0.1.0", path = "../span" }
pxp-type = { version = "0.1.0", path = "../type" }
7 changes: 7 additions & 0 deletions crates/analyser/src/ast/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use pxp_ast::{ReturnStatement, Statement};

mod return_finder;

pub(crate) fn find_returns_in_block<'a>(block: &'a [Statement]) -> Vec<&'a ReturnStatement> {
return_finder::ReturnFinder::find(block)
}
31 changes: 31 additions & 0 deletions crates/analyser/src/ast/return_finder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use pxp_ast::{visitor::{Ancestors, NodeVisitor, NodeVisitorEscapeHatch, Visitor}, Node, ReturnStatement, Statement};

pub(super) struct ReturnFinder<'a> {
returns: Vec<&'a ReturnStatement>,
}

impl<'a> ReturnFinder<'a> {
fn new() -> Self {
Self {
returns: Vec::new(),
}
}

pub(super) fn find(ast: &'a [Statement]) -> Vec<&'a ReturnStatement> {
let mut finder = Self::new();
finder.traverse(ast);
finder.returns
}
}

impl<'a> NodeVisitor<'a> for ReturnFinder<'a> {
fn enter(&mut self, node: Node<'a>, _: &mut Ancestors<'a>) -> NodeVisitorEscapeHatch {
if !node.is_return_statement() {
return NodeVisitorEscapeHatch::Continue;
}

self.returns.push(node.as_return_statement().unwrap());

NodeVisitorEscapeHatch::Continue
}
}
10 changes: 10 additions & 0 deletions crates/analyser/src/collection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use crate::rule::Rule;

/// An API for collecting analysis rules into smaller groups.
pub trait RuleCollection {
/// Returns a collection of analysis rules.
fn rules(&self) -> Vec<Box<dyn Rule>>;

/// Returns the name of the collection.
fn name(&self) -> &'static str;
}
27 changes: 27 additions & 0 deletions crates/analyser/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use pxp_ast::{Node, ResolvedName};
use pxp_diagnostics::Severity;
use pxp_inference::TypeMap;
use pxp_span::Span;
use pxp_type::Type;

use crate::{AnalyserDiagnostic, Reporter};

pub struct AnalyserContext<'a> {
reporter: &'a mut Reporter,
types: TypeMap,
file: usize,
}

impl<'a> AnalyserContext<'a> {
pub fn new(reporter: &'a mut Reporter, types: TypeMap, file: usize) -> Self {
Self { reporter, types, file }
}

pub fn report(&mut self, diagnostic: AnalyserDiagnostic, severity: Severity, span: Span) {
self.reporter.report(self.file, diagnostic, severity, span);
}

pub fn get_type(&self, node: &Node) -> &Type<ResolvedName> {
self.types.resolve(node.id)
}
}
25 changes: 12 additions & 13 deletions crates/analyser/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
mod rule;
mod collection;
pub mod rules;
mod runner;
mod reporter;
mod context;
mod ast;

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
pub use rule::Rule;
pub use collection::RuleCollection;
pub use runner::Runner;
pub use reporter::{Reporter, AnalyserDiagnostic};
pub use context::AnalyserContext;
100 changes: 100 additions & 0 deletions crates/analyser/src/reporter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use std::collections::HashMap;

use pxp_diagnostics::{Diagnostic, DiagnosticKind, DiagnosticLabel, Severity};
use pxp_span::Span;

#[derive(Debug)]
pub struct Reporter {
diagnostics: HashMap<usize, Vec<Diagnostic<AnalyserDiagnostic>>>,
}

#[derive(Debug)]
pub struct AnalyserDiagnostic {
code: String,
identifier: String,
message: String,
help: Option<String>,
labels: Vec<DiagnosticLabel>,
}

impl AnalyserDiagnostic {
pub fn new() -> Self {
Self {
code: String::new(),
identifier: String::new(),
message: String::new(),
help: None,
labels: Vec::new(),
}
}

pub fn code(mut self, code: &str) -> Self {
self.code = code.to_string();
self
}

pub fn identifier(mut self, identifier: &str) -> Self {
self.identifier = identifier.to_string();
self
}

pub fn message(mut self, message: &str) -> Self {
self.message = message.to_string();
self
}

pub fn help(mut self, help: &str) -> Self {
self.help = Some(help.to_string());
self
}

pub fn label(mut self, label: DiagnosticLabel) -> Self {
self.labels.push(label);
self
}

pub fn labels(mut self, labels: Vec<DiagnosticLabel>) -> Self {
self.labels = labels;
self
}
}

impl DiagnosticKind for AnalyserDiagnostic {
fn get_code(&self) -> String {
self.code.clone()
}

fn get_identifier(&self) -> String {
self.identifier.clone()
}

fn get_message(&self) -> String {
self.message.clone()
}

fn get_help(&self) -> Option<String> {
self.help.clone()
}

fn get_labels(&self) -> Vec<DiagnosticLabel> {
self.labels.clone()
}
}

impl Reporter {
pub fn new() -> Self {
Self {
diagnostics: HashMap::new(),
}
}

pub fn report(&mut self, file: usize, diagnostic: AnalyserDiagnostic, severity: Severity, span: Span) {
let diagnostics = self.diagnostics.entry(file).or_default();

diagnostics.push(Diagnostic::new(diagnostic, severity, span));
}

pub fn all(&self) -> &HashMap<usize, Vec<Diagnostic<AnalyserDiagnostic>>> {
&self.diagnostics
}
}
14 changes: 14 additions & 0 deletions crates/analyser/src/rule.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use pxp_ast::{visitor::Ancestors, Node};

use crate::AnalyserContext;

pub trait Rule {
/// Determine if the rule should be execute for the given node and ancestor tree.
fn should_run(&self, node: &Node, ancestors: &Ancestors) -> bool;

/// Execute the rule for the given node and ancestor tree.
fn run(&self, node: &Node, ancestors: &Ancestors, context: &mut AnalyserContext);

/// Returns the name of the rule.
fn name(&self) -> &'static str;
}
2 changes: 2 additions & 0 deletions crates/analyser/src/rules/functions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod return_type;

19 changes: 19 additions & 0 deletions crates/analyser/src/rules/functions/return_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use pxp_ast::{visitor::Ancestors, Node};

use crate::Rule;

pub struct ReturnTypeRule;

impl Rule for ReturnTypeRule {
fn should_run(&self, node: &Node, ancestors: &Ancestors) -> bool {

}

fn run(&self, node: &Node, ancestors: &Ancestors, context: &mut crate::AnalyserContext) {

}

fn name(&self) -> &'static str {
"functions/return_type"
}
}
1 change: 1 addition & 0 deletions crates/analyser/src/rules/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod functions;
61 changes: 61 additions & 0 deletions crates/analyser/src/runner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::{path::Path, task::Context};

use pxp_ast::{visitor::{Ancestors, NodeVisitor, NodeVisitorEscapeHatch}, Node, Statement};
use pxp_inference::TypeEngine;

use crate::{AnalyserContext, Reporter, Rule, RuleCollection};

pub struct Runner<'a> {
rules: Vec<Box<dyn Rule>>,
type_engine: &'a TypeEngine<'a>,
}

impl<'a> Runner<'a> {
pub fn new(type_engine: &'a TypeEngine) -> Self {
Self {
rules: Vec::new(),
type_engine,
}
}

pub fn add_rule(&mut self, rule: impl Rule + 'static) {
self.rules.push(Box::new(rule));
}

pub fn add_collection(&mut self, collection: impl RuleCollection + 'static) {
for rule in collection.rules() {
self.rules.push(rule);
}
}

pub fn run(&self, file: usize, reporter: &mut Reporter, ast: &[Statement]) {
let types = self.type_engine.infer(ast);
let mut context = AnalyserContext::new(reporter, types, file);
let mut visitor = AnalyserVisitor::new(self, &mut context);

visitor.traverse(ast);
}
}

struct AnalyserVisitor<'a> {
runner: &'a Runner<'a>,
context: &'a mut AnalyserContext<'a>,
}

impl<'a> AnalyserVisitor<'a> {
fn new(runner: &'a Runner, context: &'a mut AnalyserContext<'a>) -> Self {
Self { runner, context }
}
}

impl<'a> NodeVisitor<'a> for AnalyserVisitor<'a> {
fn enter(&mut self, node: Node<'a>, ancestors: &mut Ancestors<'a>) -> NodeVisitorEscapeHatch {
for rule in &self.runner.rules {
if rule.should_run(&node, ancestors) {
rule.run(&node, ancestors, self.context);
}
}

NodeVisitorEscapeHatch::Continue
}
}
42 changes: 38 additions & 4 deletions crates/diagnostics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,46 @@ use pxp_span::Span;
pub use severity::*;

pub trait DiagnosticKind {
fn code(&self) -> String;
fn identifier(&self) -> String;
fn message(&self) -> String;
fn help(&self) -> Option<String> {
fn get_code(&self) -> String;
fn get_identifier(&self) -> String;
fn get_message(&self) -> String;
fn get_help(&self) -> Option<String> {
None
}
fn get_labels(&self) -> Vec<DiagnosticLabel> {
Vec::new()
}
}

#[derive(Debug, Clone)]
pub struct DiagnosticLabel {
pub style: DiagnosticLabelStyle,
pub span: Span,
pub message: String,
}

impl DiagnosticLabel {
pub fn new(style: DiagnosticLabelStyle, span: Span, message: impl Into<String>) -> Self {
Self {
style,
span,
message: message.into(),
}
}

pub fn primary(span: Span, message: impl Into<String>) -> Self {
Self::new(DiagnosticLabelStyle::Primary, span, message)
}

pub fn secondary(span: Span, message: impl Into<String>) -> Self {
Self::new(DiagnosticLabelStyle::Secondary, span, message)
}
}

#[derive(Debug, Clone, Copy)]
pub enum DiagnosticLabelStyle {
Primary,
Secondary,
}

#[derive(Debug, Clone)]
Expand Down
Loading

0 comments on commit a6ee6ad

Please sign in to comment.