From 8f13a6fdb1771819510c764d68c0d0aa4acec03e Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 24 May 2024 17:48:10 +0200 Subject: [PATCH] chore: extract AST validation to its own module --- Cargo.lock | 1 + .../src/diagnostics/emitter/human.rs | 8 +- .../interface/src/diagnostics/emitter/json.rs | 4 +- .../interface/src/diagnostics/emitter/mod.rs | 2 +- crates/interface/src/span.rs | 3 + crates/sema/Cargo.toml | 1 + crates/sema/src/ast_validation.rs | 76 +++++++++++++++++++ crates/sema/src/lib.rs | 53 ++++--------- 8 files changed, 104 insertions(+), 44 deletions(-) create mode 100644 crates/sema/src/ast_validation.rs diff --git a/Cargo.lock b/Cargo.lock index 1d8bd15f..182a5f5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2264,6 +2264,7 @@ dependencies = [ name = "sulk-sema" version = "0.0.0" dependencies = [ + "rayon", "sulk-ast", "sulk-data-structures", "sulk-interface", diff --git a/crates/interface/src/diagnostics/emitter/human.rs b/crates/interface/src/diagnostics/emitter/human.rs index 52130dd0..b375e33b 100644 --- a/crates/interface/src/diagnostics/emitter/human.rs +++ b/crates/interface/src/diagnostics/emitter/human.rs @@ -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`, without any auto-traits. + // We must implement them manually and enforce them through the public API. writer: AutoStream>, source_map: Option>, 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| { @@ -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, color: ColorChoice) -> Self { + pub fn new(writer: Box, color: ColorChoice) -> Self { Self { + // NOTE: Intentionally erases the `+ Send` bound, see `writer` above. writer: AutoStream::new(writer, color), source_map: None, renderer: DEFAULT_RENDERER, diff --git a/crates/interface/src/diagnostics/emitter/json.rs b/crates/interface/src/diagnostics/emitter/json.rs index b355b992..92a1576a 100644 --- a/crates/interface/src/diagnostics/emitter/json.rs +++ b/crates/interface/src/diagnostics/emitter/json.rs @@ -14,7 +14,7 @@ use sulk_data_structures::sync::Lrc; /// Diagnostic emitter that emits diagnostics as JSON. pub struct JsonEmitter { - writer: Box, + writer: Box, pretty: bool, rustc_like: bool, @@ -41,7 +41,7 @@ impl Emitter for JsonEmitter { impl JsonEmitter { /// Creates a new `JsonEmitter` that writes to given writer. - pub fn new(writer: Box, source_map: Lrc) -> Self { + pub fn new(writer: Box, source_map: Lrc) -> Self { let buffer = LocalBuffer::new(); Self { writer, diff --git a/crates/interface/src/diagnostics/emitter/mod.rs b/crates/interface/src/diagnostics/emitter/mod.rs index d3062cf6..d26d98c9 100644 --- a/crates/interface/src/diagnostics/emitter/mod.rs +++ b/crates/interface/src/diagnostics/emitter/mod.rs @@ -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 { diff --git a/crates/interface/src/span.rs b/crates/interface/src/span.rs index 96345c95..a9ac3444 100644 --- a/crates/interface/src/span.rs +++ b/crates/interface/src/span.rs @@ -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())) } @@ -153,6 +154,7 @@ impl Span { /// self lorem ipsum end /// ^^^^^^^^^^^^^ /// ``` + #[inline] pub fn between(self, end: Self) -> Self { Self::new(self.hi(), end.lo()) } @@ -164,6 +166,7 @@ impl Span { /// self lorem ipsum end /// ^^^^^^^^^^^^^^^^^ /// ``` + #[inline] pub fn until(self, end: Self) -> Self { Self::new(self.lo(), end.lo()) } diff --git a/crates/sema/Cargo.toml b/crates/sema/Cargo.toml index fa1a1fa1..92f28e69 100644 --- a/crates/sema/Cargo.toml +++ b/crates/sema/Cargo.toml @@ -21,6 +21,7 @@ sulk-data-structures.workspace = true sulk-interface.workspace = true sulk-parse.workspace = true +rayon.workspace = true tracing.workspace = true [features] diff --git a/crates/sema/src/ast_validation.rs b/crates/sema/src/ast_validation.rs new file mode 100644 index 00000000..257d0ccf --- /dev/null +++ b/crates/sema/src/ast_validation.rs @@ -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) {} +} diff --git a/crates/sema/src/lib.rs b/crates/sema/src/lib.rs index 17787473..bb5e8904 100644 --- a/crates/sema/src/lib.rs +++ b/crates/sema/src/lib.rs @@ -9,6 +9,7 @@ #[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}; @@ -16,12 +17,15 @@ 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 @@ -56,6 +60,11 @@ impl Sources { fn asts(&self) -> impl DoubleEndedIterator { self.0.iter().filter_map(|source| source.ast.as_ref()) } + + #[allow(dead_code)] + fn par_asts(&self) -> impl ParallelIterator { + self.0.as_raw_slice().par_iter().filter_map(|source| source.ast.as_ref()) + } } struct Source { @@ -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(()) } @@ -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)); } }