Skip to main content

solar_sema/
lib.rs

1#![doc = include_str!("../README.md")]
2#![doc(
3    html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/solar/main/assets/logo.png",
4    html_favicon_url = "https://raw.githubusercontent.com/paradigmxyz/solar/main/assets/favicon.ico"
5)]
6#![cfg_attr(feature = "nightly", feature(rustc_attrs), allow(internal_features))]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8
9#[macro_use]
10extern crate tracing;
11
12use rayon::prelude::*;
13use solar_interface::{Result, Session, config::CompilerStage};
14use std::ops::ControlFlow;
15
16// Convenience re-exports.
17#[doc(no_inline)]
18pub use ::thread_local;
19#[doc(no_inline)]
20pub use bumpalo;
21pub use solar_ast as ast;
22pub use solar_interface as interface;
23
24mod ast_lowering;
25mod ast_passes;
26mod natspec;
27
28mod compiler;
29pub use compiler::{Compiler, CompilerRef};
30
31mod parse;
32pub use parse::{ParsingContext, Source, Sources};
33
34pub mod builtins;
35pub mod eval;
36
37pub mod hir;
38pub use hir::Hir;
39
40pub mod ty;
41pub use ty::{Gcx, Ty};
42
43mod typeck;
44
45pub mod stats;
46
47mod span_visitor;
48
49pub(crate) fn lower(compiler: &mut CompilerRef<'_>) -> Result<ControlFlow<()>> {
50    let gcx = compiler.gcx();
51    let sess = gcx.sess;
52
53    if gcx.sources.is_empty() {
54        debug!("no files found");
55        return Ok(ControlFlow::Break(()));
56    }
57
58    if let Some(dump) = &sess.opts.unstable.dump
59        && dump.kind.is_ast()
60    {
61        dump_ast(sess, &gcx.sources, dump.paths.as_deref())?;
62    }
63
64    if sess.opts.unstable.ast_stats {
65        for source in gcx.sources.asts() {
66            stats::print_ast_stats(source, "AST STATS");
67        }
68    }
69
70    if sess.opts.unstable.span_visitor {
71        use crate::span_visitor::SpanVisitor;
72        use ast::visit::Visit;
73        for source in gcx.sources.asts() {
74            let mut visitor = SpanVisitor::new(sess);
75            let _ = visitor.visit_source_unit(source);
76            debug!(spans_visited = visitor.count(), "span visitor completed");
77        }
78    }
79
80    if sess.opts.language.is_yul() || gcx.advance_stage(CompilerStage::Lowering).is_break() {
81        return Ok(ControlFlow::Break(()));
82    }
83
84    compiler.gcx_mut().sources.topo_sort();
85
86    debug_span!("all_ast_passes").in_scope(|| {
87        gcx.sources.par_asts().for_each(|ast| {
88            ast_passes::run(gcx.sess, ast);
89        });
90    });
91
92    ast_lowering::lower(compiler.gcx_mut());
93
94    Ok(ControlFlow::Continue(()))
95}
96
97#[instrument(level = "debug", skip_all)]
98fn analysis(gcx: Gcx<'_>) -> Result<ControlFlow<()>> {
99    if let ControlFlow::Break(()) = gcx.advance_stage(CompilerStage::Analysis) {
100        return Ok(ControlFlow::Break(()));
101    }
102
103    if let Some(dump) = &gcx.sess.opts.unstable.dump
104        && dump.kind.is_hir()
105    {
106        dump_hir(gcx, dump.paths.as_deref())?;
107    }
108
109    if gcx.sess.opts.unstable.hir_stats {
110        stats::print_hir_stats(&gcx.hir, "HIR STATS");
111    }
112
113    // Lower HIR types.
114    gcx.hir.par_item_ids().for_each(|id| {
115        let _ = gcx.type_of_item(id);
116        match id {
117            hir::ItemId::Struct(id) => {
118                let _ = gcx.struct_recursiveness(id);
119                let _ = gcx.struct_field_types(id);
120            }
121            hir::ItemId::Contract(id) => _ = gcx.interface_functions(id),
122            _ => {}
123        }
124        natspec::validate_item_docs(gcx, id);
125    });
126
127    typeck::check(gcx);
128
129    Ok(ControlFlow::Continue(()))
130}
131
132fn dump_ast(sess: &Session, sources: &Sources<'_>, paths: Option<&[String]>) -> Result<()> {
133    if let Some(paths) = paths {
134        for path in paths {
135            let sm = sess.source_map();
136            if let Some(file) = sm.get_file(sm.parse_file_name(path))
137                && let Some((_, source)) = sources.get_file(&file)
138            {
139                println!("{source:#?}");
140            } else {
141                let msg = format!("`-Zdump=ast={path}` did not match any source file");
142                let note = format!(
143                    "available source files: {}",
144                    sources
145                        .iter()
146                        .map(|s| s.file.name.display().to_string())
147                        .collect::<Vec<_>>()
148                        .join(", ")
149                );
150                return Err(sess.dcx.err(msg).note(note).emit());
151            }
152        }
153    } else {
154        println!("{sources:#?}");
155    }
156
157    Ok(())
158}
159
160fn dump_hir(gcx: Gcx<'_>, paths: Option<&[String]>) -> Result<()> {
161    if let Some(paths) = paths {
162        let mut printer = hir::HirPrinter::new(gcx);
163        for path in paths {
164            if let Some((id, source)) = gcx.get_hir_source(path.clone()) {
165                printer.print_source(id, source);
166            } else {
167                let msg = format!("`-Zdump=hir={path}` did not match any source file");
168                let note = format!(
169                    "available source files: {}",
170                    gcx.hir
171                        .sources()
172                        .map(|s| s.file.name.display().to_string())
173                        .collect::<Vec<_>>()
174                        .join(", ")
175                );
176                return Err(gcx.sess.dcx.err(msg).note(note).emit());
177            }
178        }
179        print!("{}", printer.finish());
180    } else {
181        print!("{}", hir::HirPrinter::new(gcx).print_all());
182    }
183    Ok(())
184}
185
186fn fmt_bytes(bytes: usize) -> impl std::fmt::Display {
187    solar_data_structures::fmt::from_fn(move |f| {
188        let mut size = bytes as f64;
189        let mut suffix = "B";
190        if size >= 1024.0 {
191            size /= 1024.0;
192            suffix = "KiB";
193        }
194        if size >= 1024.0 {
195            size /= 1024.0;
196            suffix = "MiB";
197        }
198        if size >= 1024.0 {
199            size /= 1024.0;
200            suffix = "GiB";
201        }
202
203        let precision = if size.fract() != 0.0 { 2 } else { 0 };
204        write!(f, "{size:.precision$} {suffix}")?;
205        if suffix != "B" {
206            write!(f, " ({bytes} B)")?;
207        }
208        Ok(())
209    })
210}