Skip to content

Commit

Permalink
chore: extract AST validation to its own module
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes committed May 24, 2024
1 parent 8411a05 commit 8f13a6f
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 44 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion crates/interface/src/diagnostics/emitter/human.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ const DEFAULT_RENDERER: Renderer = Renderer::plain()

/// Diagnostic emitter that emits to an arbitrary [`io::Write`] writer in human-readable format.
pub struct HumanEmitter {
// NOTE: `AutoStream` only allows bare `Box<dyn Write>`, without any auto-traits.
// We must implement them manually and enforce them through the public API.
writer: AutoStream<Box<dyn Write>>,
source_map: Option<Lrc<SourceMap>>,
renderer: Renderer,
}

// SAFETY: See `writer` above.
unsafe impl Send for HumanEmitter {}

impl Emitter for HumanEmitter {
fn emit_diagnostic(&mut self, diagnostic: &Diagnostic) {
self.snippet(diagnostic, |this, snippet| {
Expand Down Expand Up @@ -56,8 +61,9 @@ impl HumanEmitter {
/// Note that a color choice of `Auto` will be treated as `Never` because the writer opaque
/// at this point. Prefer calling [`AutoStream::choice`] on the writer if it is known
/// before-hand.
pub fn new(writer: Box<dyn Write>, color: ColorChoice) -> Self {
pub fn new(writer: Box<dyn Write + Send>, color: ColorChoice) -> Self {
Self {
// NOTE: Intentionally erases the `+ Send` bound, see `writer` above.
writer: AutoStream::new(writer, color),
source_map: None,
renderer: DEFAULT_RENDERER,
Expand Down
4 changes: 2 additions & 2 deletions crates/interface/src/diagnostics/emitter/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use sulk_data_structures::sync::Lrc;

/// Diagnostic emitter that emits diagnostics as JSON.
pub struct JsonEmitter {
writer: Box<dyn io::Write>,
writer: Box<dyn io::Write + Send>,
pretty: bool,
rustc_like: bool,

Expand All @@ -41,7 +41,7 @@ impl Emitter for JsonEmitter {

impl JsonEmitter {
/// Creates a new `JsonEmitter` that writes to given writer.
pub fn new(writer: Box<dyn io::Write>, source_map: Lrc<SourceMap>) -> Self {
pub fn new(writer: Box<dyn io::Write + Send>, source_map: Lrc<SourceMap>) -> Self {
let buffer = LocalBuffer::new();
Self {
writer,
Expand Down
2 changes: 1 addition & 1 deletion crates/interface/src/diagnostics/emitter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub use json::JsonEmitter;
mod rustc;

/// Dynamic diagnostic emitter. See [`Emitter`].
pub type DynEmitter = dyn Emitter;
pub type DynEmitter = dyn Emitter + Send;

/// Diagnostic emitter.
pub trait Emitter {
Expand Down
3 changes: 3 additions & 0 deletions crates/interface/src/span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ impl Span {
/// self lorem ipsum end
/// ^^^^^^^^^^^^^^^^^^^^
/// ```
#[inline]
pub fn to(self, end: Self) -> Self {
Self::new(cmp::min(self.lo(), end.lo()), cmp::max(self.hi(), end.hi()))
}
Expand All @@ -153,6 +154,7 @@ impl Span {
/// self lorem ipsum end
/// ^^^^^^^^^^^^^
/// ```
#[inline]
pub fn between(self, end: Self) -> Self {
Self::new(self.hi(), end.lo())
}
Expand All @@ -164,6 +166,7 @@ impl Span {
/// self lorem ipsum end
/// ^^^^^^^^^^^^^^^^^
/// ```
#[inline]
pub fn until(self, end: Self) -> Self {
Self::new(self.lo(), end.lo())
}
Expand Down
1 change: 1 addition & 0 deletions crates/sema/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ sulk-data-structures.workspace = true
sulk-interface.workspace = true
sulk-parse.workspace = true

rayon.workspace = true
tracing.workspace = true

[features]
Expand Down
76 changes: 76 additions & 0 deletions crates/sema/src/ast_validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use sulk_ast::{ast, visit::Visit};
use sulk_interface::{diagnostics::DiagCtxt, sym, Session, Span};

// TODO: Implement the rest.

/// AST validator.
pub struct AstValidator<'sess> {
span: Span,
dcx: &'sess DiagCtxt,
}

impl<'sess> AstValidator<'sess> {
/// Creates a new AST validator. Valid only for one AST.
pub fn new(sess: &'sess Session) -> Self {
Self { span: Span::DUMMY, dcx: &sess.dcx }
}

/// Performs AST validation.
pub fn validate(sess: &'sess Session, source_unit: &ast::SourceUnit) {
let mut validator = Self::new(sess);
validator.visit_source_unit(source_unit);
}

/// Returns the diagnostics context.
#[inline]
pub fn dcx(&self) -> &'sess DiagCtxt {
self.dcx
}
}

impl<'ast, 'sess> Visit<'ast> for AstValidator<'sess> {
fn visit_item(&mut self, item: &'ast ast::Item) {
self.span = item.span;
self.walk_item(item);
}

fn visit_pragma_directive(&mut self, pragma: &'ast ast::PragmaDirective) {
match &pragma.tokens {
ast::PragmaTokens::Version(name, _version) => {
if name.name != sym::solidity {
let msg = "only `solidity` is supported as a version pragma";
self.dcx().err(msg).span(name.span).emit();
// return;
}
// TODO: Check or ignore version?
}
ast::PragmaTokens::Custom(name, value) => {
let name = name.as_str();
let value = value.as_ref().map(ast::IdentOrStrLit::as_str);
match (name, value) {
("abicoder", Some("v1" | "v2")) => {}
("experimental", Some("ABIEncoderV2")) => {}
("experimental", Some("SMTChecker")) => {}
("experimental", Some("solidity")) => {
let msg = "experimental solidity features are not supported";
self.dcx().err(msg).span(self.span).emit();
}
_ => {
self.dcx().err("unknown pragma").span(self.span).emit();
}
}
}
ast::PragmaTokens::Verbatim(_) => {
self.dcx().err("unknown pragma").span(self.span).emit();
}
}
}

// Intentionally override unused default implementations to reduce bloat.

fn visit_expr(&mut self, _expr: &'ast ast::Expr) {}

fn visit_stmt(&mut self, _stmt: &'ast ast::Stmt) {}

fn visit_ty(&mut self, _ty: &'ast ast::Ty) {}
}
53 changes: 13 additions & 40 deletions crates/sema/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@
#[macro_use]
extern crate tracing;

use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use std::path::{Path, PathBuf};
use sulk_ast::ast;
use sulk_data_structures::{index::IndexVec, newtype_index, sync::Lrc};
use sulk_interface::{
debug_time,
diagnostics::DiagCtxt,
source_map::{FileName, FileResolver, ResolveError, SourceFile},
sym, trace_time, Result, Session, Span,
trace_time, Result, Session,
};
use sulk_parse::{Lexer, Parser};

// pub mod hir;

mod ast_validation;
pub use ast_validation::AstValidator;

newtype_index! {
/// A source index.
pub(crate) struct SourceId
Expand Down Expand Up @@ -56,6 +60,11 @@ impl Sources {
fn asts(&self) -> impl DoubleEndedIterator<Item = &ast::SourceUnit> {
self.0.iter().filter_map(|source| source.ast.as_ref())
}

#[allow(dead_code)]
fn par_asts(&self) -> impl ParallelIterator<Item = &ast::SourceUnit> {
self.0.as_raw_slice().par_iter().filter_map(|source| source.ast.as_ref())
}
}

struct Source {
Expand Down Expand Up @@ -129,14 +138,7 @@ impl<'a> Resolver<'a> {
return Ok(());
}

// TODO: Proper AST validation and in parallel.
for ast in self.sources.asts() {
for item in &ast.items {
if let ast::ItemKind::Pragma(pragma) = &item.kind {
self.check_pragma(item.span, pragma);
}
}
}
debug_time!("validate ASTs", || self.validate_asts());

Ok(())
}
Expand Down Expand Up @@ -193,36 +195,7 @@ impl<'a> Resolver<'a> {
debug!("parsed {} files", self.sources.0.len());
}

// TODO: Move to AST validation.
fn check_pragma(&self, span: Span, pragma: &ast::PragmaDirective) {
match &pragma.tokens {
ast::PragmaTokens::Version(name, _version) => {
if name.name != sym::solidity {
let msg = "only `solidity` is supported as a version pragma";
self.dcx().err(msg).span(name.span).emit();
// return;
}
// TODO: Check or ignore version?
}
ast::PragmaTokens::Custom(name, value) => {
let name = name.as_str();
let value = value.as_ref().map(ast::IdentOrStrLit::as_str);
match (name, value) {
("abicoder", Some("v1" | "v2")) => {}
("experimental", Some("ABIEncoderV2")) => {}
("experimental", Some("SMTChecker")) => {}
("experimental", Some("solidity")) => {
let msg = "experimental solidity features are not supported";
self.dcx().err(msg).span(span).emit();
}
_ => {
self.dcx().err("unknown pragma").span(span).emit();
}
}
}
ast::PragmaTokens::Verbatim(_) => {
self.dcx().err("unknown pragma").span(span).emit();
}
}
fn validate_asts(&self) {
self.sources.par_asts().for_each(|ast| AstValidator::validate(self.sess, ast));
}
}

0 comments on commit 8f13a6f

Please sign in to comment.