From 964b0545260720d71ed092e8fc340be4cfd1c334 Mon Sep 17 00:00:00 2001 From: grandizzy Date: Mon, 24 Feb 2025 20:47:58 +0200 Subject: [PATCH 1/5] feat(hir): expose hir from parsing context --- crates/sema/src/lib.rs | 24 ++++++++++++++++++++++ crates/sema/src/parse.rs | 6 +++++- examples/src/AnotherCounter.sol | 20 ++++++++++++++++++ examples/src/hir.rs | 36 +++++++++++++++++++++++++++++++++ examples/src/lib.rs | 1 + 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 examples/src/AnotherCounter.sol create mode 100644 examples/src/hir.rs diff --git a/crates/sema/src/lib.rs b/crates/sema/src/lib.rs index b312ea40..bde28dd3 100644 --- a/crates/sema/src/lib.rs +++ b/crates/sema/src/lib.rs @@ -25,6 +25,7 @@ mod ast_lowering; mod ast_passes; mod parse; +use crate::hir::{Arena, Hir}; pub use parse::{ParsedSource, ParsedSources, ParsingContext}; pub mod builtins; @@ -100,6 +101,29 @@ pub fn parse_and_resolve(pcx: ParsingContext<'_>) -> Result<()> { Ok(()) } +/// Parses and lowers to HIR, recursing into imports. +pub fn parse_and_lower_to_hir<'hir>(pcx: ParsingContext<'_>, hir_arena: &'hir Arena) -> Result> { + let sess = pcx.sess; + + if pcx.sources.is_empty() { + let msg = "no files found"; + let note = "if you wish to use the standard input, please specify `-` explicitly"; + return Err(sess.dcx.err(msg).note(note).emit()); + } + + let ast_arenas = OnDrop::new(ThreadLocal::::new(), |mut arenas| { + debug!(asts_allocated = arenas.iter_mut().map(|a| a.allocated_bytes()).sum::()); + debug_span!("dropping_ast_arenas").in_scope(|| drop(arenas)); + }); + let mut sources = pcx.parse(&ast_arenas); + + sources.topo_sort(); + + let (hir, _) = lower(sess, &sources, hir_arena)?; + + Ok(hir) +} + /// Lowers the parsed ASTs into the HIR. fn lower<'sess, 'hir>( sess: &'sess Session, diff --git a/crates/sema/src/parse.rs b/crates/sema/src/parse.rs index ccb596c7..82e441f4 100644 --- a/crates/sema/src/parse.rs +++ b/crates/sema/src/parse.rs @@ -1,4 +1,4 @@ -use crate::hir::SourceId; +use crate::hir::{Arena, Hir, SourceId}; use rayon::prelude::*; use solar_ast as ast; use solar_data_structures::{ @@ -90,6 +90,10 @@ impl<'sess> ParsingContext<'sess> { crate::parse_and_resolve(self) } + pub fn parse_and_lower_to_hir(self, hir_arena: &Arena) -> Result> { + crate::parse_and_lower_to_hir(self, hir_arena) + } + /// Parses all the loaded sources, recursing into imports. /// /// Sources are not guaranteed to be in any particular order, as they may be parsed in parallel. diff --git a/examples/src/AnotherCounter.sol b/examples/src/AnotherCounter.sol new file mode 100644 index 00000000..96c2fffd --- /dev/null +++ b/examples/src/AnotherCounter.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "./Counter.sol"; + +contract AnotherCounter { + Counter counter = new Counter(); + + constructor(Counter _counter) { + counter = _counter; + } + + function setNumber(uint256 newNumber) public { + counter.setNumber(newNumber); + } + + function increment() public { + counter.increment(); + } +} diff --git a/examples/src/hir.rs b/examples/src/hir.rs new file mode 100644 index 00000000..c4334d35 --- /dev/null +++ b/examples/src/hir.rs @@ -0,0 +1,36 @@ +use solar::{ + interface::{diagnostics::EmittedDiagnostics, Session}, + sema::{ + hir::{Arena, ContractId, Hir}, + ParsingContext, + }, +}; +use std::path::Path; + +#[test] +fn main() -> Result<(), EmittedDiagnostics> { + let paths = vec![Path::new("src/AnotherCounter.sol")]; + + // Create a new session with a buffer emitter. + // This is required to capture the emitted diagnostics and to return them at the end. + let sess = Session::builder().with_buffer_emitter(solar::interface::ColorChoice::Auto).build(); + + // Enter the context and parse the file. + // Counter will be parsed, even if not explicitly provided, since it is a dependency. + let _ = sess.enter_parallel(|| -> solar::interface::Result<()> { + // Set up the parser. + let hir_arena = Arena::new(); + let mut parsing_context = ParsingContext::new(&sess); + parsing_context.load_files(paths)?; + let hir = parsing_context.parse_and_lower_to_hir(&hir_arena)?; + let counter_contract = Hir::contract(&hir, ContractId::new(0)); + assert_eq!(counter_contract.name.to_string(), "Counter"); + let another_counter_contract = Hir::contract(&hir, ContractId::new(1)); + assert_eq!(another_counter_contract.name.to_string(), "AnotherCounter"); + Ok(()) + }); + + // Return the emitted diagnostics as a `Result<(), _>`. + // If any errors were emitted, this returns `Err(_)`, otherwise `Ok(())`. + sess.emitted_errors().unwrap() +} diff --git a/examples/src/lib.rs b/examples/src/lib.rs index a65256bd..c522f3dc 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -3,4 +3,5 @@ #![allow(unreachable_pub)] #![cfg(test)] +mod hir; mod parser; From 0c17b7b7e5f85705dd7ff1fde7dc0bd182b5c07c Mon Sep 17 00:00:00 2001 From: grandizzy Date: Thu, 6 Mar 2025 12:54:14 +0200 Subject: [PATCH 2/5] Nit --- examples/src/hir.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/src/hir.rs b/examples/src/hir.rs index c4334d35..0ab16ddd 100644 --- a/examples/src/hir.rs +++ b/examples/src/hir.rs @@ -1,7 +1,7 @@ use solar::{ interface::{diagnostics::EmittedDiagnostics, Session}, sema::{ - hir::{Arena, ContractId, Hir}, + hir::{Arena, ContractId}, ParsingContext, }, }; @@ -23,9 +23,9 @@ fn main() -> Result<(), EmittedDiagnostics> { let mut parsing_context = ParsingContext::new(&sess); parsing_context.load_files(paths)?; let hir = parsing_context.parse_and_lower_to_hir(&hir_arena)?; - let counter_contract = Hir::contract(&hir, ContractId::new(0)); + let counter_contract = hir.contract(ContractId::new(0)); assert_eq!(counter_contract.name.to_string(), "Counter"); - let another_counter_contract = Hir::contract(&hir, ContractId::new(1)); + let another_counter_contract = hir.contract(ContractId::new(1)); assert_eq!(another_counter_contract.name.to_string(), "AnotherCounter"); Ok(()) }); @@ -34,3 +34,4 @@ fn main() -> Result<(), EmittedDiagnostics> { // If any errors were emitted, this returns `Err(_)`, otherwise `Ok(())`. sess.emitted_errors().unwrap() } + From ec3430dfb6b97d6ca078ea73997cfa51a912249d Mon Sep 17 00:00:00 2001 From: grandizzy Date: Mon, 10 Mar 2025 09:15:59 +0200 Subject: [PATCH 3/5] Fix fmt, add command in contributing guide --- CONTRIBUTING.md | 1 + crates/sema/src/lib.rs | 5 ++++- examples/src/hir.rs | 1 - 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 54355b1b..81dca0c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,6 +126,7 @@ to ignore benchmarks and examples: ``` cargo check --workspace cargo clippy --workspace +cargo fmt --all --check cargo test --workspace ``` diff --git a/crates/sema/src/lib.rs b/crates/sema/src/lib.rs index bde28dd3..90c4476e 100644 --- a/crates/sema/src/lib.rs +++ b/crates/sema/src/lib.rs @@ -102,7 +102,10 @@ pub fn parse_and_resolve(pcx: ParsingContext<'_>) -> Result<()> { } /// Parses and lowers to HIR, recursing into imports. -pub fn parse_and_lower_to_hir<'hir>(pcx: ParsingContext<'_>, hir_arena: &'hir Arena) -> Result> { +pub fn parse_and_lower_to_hir<'hir>( + pcx: ParsingContext<'_>, + hir_arena: &'hir Arena, +) -> Result> { let sess = pcx.sess; if pcx.sources.is_empty() { diff --git a/examples/src/hir.rs b/examples/src/hir.rs index 0ab16ddd..928a4a4f 100644 --- a/examples/src/hir.rs +++ b/examples/src/hir.rs @@ -34,4 +34,3 @@ fn main() -> Result<(), EmittedDiagnostics> { // If any errors were emitted, this returns `Err(_)`, otherwise `Ok(())`. sess.emitted_errors().unwrap() } - From 9c654ccb33690e5664274e324dadf80e779aa369 Mon Sep 17 00:00:00 2001 From: grandizzy Date: Tue, 11 Mar 2025 09:58:54 +0200 Subject: [PATCH 4/5] Changes after review --- crates/sema/src/lib.rs | 59 +++++++++++++++++++-------------------- crates/sema/src/parse.rs | 10 +++++-- crates/sema/src/ty/mod.rs | 5 ++++ examples/src/hir.rs | 19 +++++++++---- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/crates/sema/src/lib.rs b/crates/sema/src/lib.rs index 90c4476e..9539fb54 100644 --- a/crates/sema/src/lib.rs +++ b/crates/sema/src/lib.rs @@ -25,12 +25,14 @@ mod ast_lowering; mod ast_passes; mod parse; -use crate::hir::{Arena, Hir}; pub use parse::{ParsedSource, ParsedSources, ParsingContext}; pub mod builtins; pub mod eval; + pub mod hir; +pub use hir::{Arena, Hir}; + pub mod ty; mod typeck; @@ -41,6 +43,17 @@ pub mod stats; /// Parses and semantically analyzes all the loaded sources, recursing into imports. pub fn parse_and_resolve(pcx: ParsingContext<'_>) -> Result<()> { + let _ = parse_and_lower(pcx, None)?; + + Ok(()) +} + +/// Parses and lowers to HIR, recursing into imports. If called with an external HIR arena then +/// returns the global compilation context (which can be used to access HIR). +pub fn parse_and_lower<'hir>( + pcx: ParsingContext<'hir>, + hir_arena: Option<&'hir ThreadLocal>, +) -> Result>> { let sess = pcx.sess; if pcx.sources.is_empty() { @@ -54,6 +67,17 @@ pub fn parse_and_resolve(pcx: ParsingContext<'_>) -> Result<()> { debug_span!("dropping_ast_arenas").in_scope(|| drop(arenas)); }); let mut sources = pcx.parse(&ast_arenas); + sources.topo_sort(); + + if let Some(hir_arena) = hir_arena { + let (hir, symbol_resolver) = lower(sess, &sources, hir_arena.get_or_default())?; + let global_context = + OnDrop::new(ty::GlobalCtxt::new(sess, hir_arena, hir, symbol_resolver), |gcx| { + debug_span!("drop_gcx").in_scope(|| drop(gcx)); + }); + let gcx = ty::Gcx::new(unsafe { trustme::decouple_lt(&global_context) }); + return Ok(Some(gcx)); + } if let Some(dump) = &sess.opts.unstable.dump { if dump.kind.is_ast() { @@ -68,15 +92,14 @@ pub fn parse_and_resolve(pcx: ParsingContext<'_>) -> Result<()> { } if sess.opts.language.is_yul() || sess.stop_after(CompilerStage::Parsed) { - return Ok(()); + return Ok(None); } - sources.topo_sort(); - let hir_arena = OnDrop::new(ThreadLocal::::new(), |hir_arena| { debug!(hir_allocated = hir_arena.get_or_default().allocated_bytes()); debug_span!("dropping_hir_arena").in_scope(|| drop(hir_arena)); }); + let (hir, symbol_resolver) = lower(sess, &sources, hir_arena.get_or_default())?; // Drop the ASTs and AST arenas in a separate thread. @@ -98,33 +121,7 @@ pub fn parse_and_resolve(pcx: ParsingContext<'_>) -> Result<()> { let gcx = ty::Gcx::new(unsafe { trustme::decouple_lt(&global_context) }); analysis(gcx)?; - Ok(()) -} - -/// Parses and lowers to HIR, recursing into imports. -pub fn parse_and_lower_to_hir<'hir>( - pcx: ParsingContext<'_>, - hir_arena: &'hir Arena, -) -> Result> { - let sess = pcx.sess; - - if pcx.sources.is_empty() { - let msg = "no files found"; - let note = "if you wish to use the standard input, please specify `-` explicitly"; - return Err(sess.dcx.err(msg).note(note).emit()); - } - - let ast_arenas = OnDrop::new(ThreadLocal::::new(), |mut arenas| { - debug!(asts_allocated = arenas.iter_mut().map(|a| a.allocated_bytes()).sum::()); - debug_span!("dropping_ast_arenas").in_scope(|| drop(arenas)); - }); - let mut sources = pcx.parse(&ast_arenas); - - sources.topo_sort(); - - let (hir, _) = lower(sess, &sources, hir_arena)?; - - Ok(hir) + Ok(None) } /// Lowers the parsed ASTs into the HIR. diff --git a/crates/sema/src/parse.rs b/crates/sema/src/parse.rs index 82e441f4..23aa2585 100644 --- a/crates/sema/src/parse.rs +++ b/crates/sema/src/parse.rs @@ -1,4 +1,4 @@ -use crate::hir::{Arena, Hir, SourceId}; +use crate::hir::{Arena, SourceId}; use rayon::prelude::*; use solar_ast as ast; use solar_data_structures::{ @@ -90,8 +90,11 @@ impl<'sess> ParsingContext<'sess> { crate::parse_and_resolve(self) } - pub fn parse_and_lower_to_hir(self, hir_arena: &Arena) -> Result> { - crate::parse_and_lower_to_hir(self, hir_arena) + pub fn parse_and_lower( + self, + hir_arena: &'sess ThreadLocal, + ) -> Result>> { + crate::parse_and_lower(self, Some(hir_arena)) } /// Parses all the loaded sources, recursing into imports. @@ -235,6 +238,7 @@ macro_rules! resolve_imports { }) }}; } +use crate::ty::Gcx; use resolve_imports; fn escape_import_path(path_str: &str) -> Option> { diff --git a/crates/sema/src/ty/mod.rs b/crates/sema/src/ty/mod.rs index 79a0f4b9..ee40c33f 100644 --- a/crates/sema/src/ty/mod.rs +++ b/crates/sema/src/ty/mod.rs @@ -185,6 +185,11 @@ impl<'gcx> Gcx<'gcx> { self.interner.arena.get_or_default() } + /// Returns the HIR. + pub fn hir(self) -> &'gcx Hir<'gcx> { + &self.hir + } + pub fn bump(self) -> &'gcx bumpalo::Bump { &self.arena().bump } diff --git a/examples/src/hir.rs b/examples/src/hir.rs index 928a4a4f..640086f2 100644 --- a/examples/src/hir.rs +++ b/examples/src/hir.rs @@ -2,6 +2,7 @@ use solar::{ interface::{diagnostics::EmittedDiagnostics, Session}, sema::{ hir::{Arena, ContractId}, + thread_local::ThreadLocal, ParsingContext, }, }; @@ -19,14 +20,20 @@ fn main() -> Result<(), EmittedDiagnostics> { // Counter will be parsed, even if not explicitly provided, since it is a dependency. let _ = sess.enter_parallel(|| -> solar::interface::Result<()> { // Set up the parser. - let hir_arena = Arena::new(); + let hir_arena = ThreadLocal::::new(); let mut parsing_context = ParsingContext::new(&sess); parsing_context.load_files(paths)?; - let hir = parsing_context.parse_and_lower_to_hir(&hir_arena)?; - let counter_contract = hir.contract(ContractId::new(0)); - assert_eq!(counter_contract.name.to_string(), "Counter"); - let another_counter_contract = hir.contract(ContractId::new(1)); - assert_eq!(another_counter_contract.name.to_string(), "AnotherCounter"); + + if let Some(gcx) = parsing_context.parse_and_lower(&hir_arena)? { + for contract in gcx.hir().contracts() { + println!("contract: {}", contract.name); + } + let counter_contract = gcx.hir().contract(ContractId::new(0)); + assert_eq!(counter_contract.name.to_string(), "Counter"); + let another_counter_contract = gcx.hir().contract(ContractId::new(1)); + assert_eq!(another_counter_contract.name.to_string(), "AnotherCounter"); + } + Ok(()) }); From 82c92181fcd9d5527918a42a099ba80ef0f6fae7 Mon Sep 17 00:00:00 2001 From: grandizzy Date: Tue, 11 Mar 2025 10:17:52 +0200 Subject: [PATCH 5/5] Nit --- crates/sema/src/parse.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/sema/src/parse.rs b/crates/sema/src/parse.rs index 23aa2585..c592a955 100644 --- a/crates/sema/src/parse.rs +++ b/crates/sema/src/parse.rs @@ -1,4 +1,7 @@ -use crate::hir::{Arena, SourceId}; +use crate::{ + hir::{Arena, SourceId}, + ty::Gcx, +}; use rayon::prelude::*; use solar_ast as ast; use solar_data_structures::{ @@ -238,7 +241,6 @@ macro_rules! resolve_imports { }) }}; } -use crate::ty::Gcx; use resolve_imports; fn escape_import_path(path_str: &str) -> Option> {