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.
17pub use ::thread_local;
18pub use bumpalo;
19pub use solar_ast as ast;
20pub use solar_interface as interface;
21
22mod ast_lowering;
23mod ast_passes;
24
25mod compiler;
26pub use compiler::{Compiler, CompilerRef};
27
28mod parse;
29pub use parse::{ParsingContext, Source, Sources};
30
31pub mod builtins;
32pub mod eval;
33
34pub mod hir;
35pub use hir::Hir;
36
37pub mod ty;
38pub use ty::{Gcx, Ty};
39
40mod typeck;
41
42mod emit;
43
44pub mod stats;
45
46mod span_visitor;
47
48pub(crate) fn lower(compiler: &mut CompilerRef<'_>) -> Result<ControlFlow<()>> {
49    let gcx = compiler.gcx();
50    let sess = gcx.sess;
51
52    if gcx.sources.is_empty() {
53        let msg = "no files found";
54        let note = "if you wish to use the standard input, please specify `-` explicitly";
55        return Err(sess.dcx.err(msg).note(note).emit());
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", "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    gcx.sess.dcx.has_errors()?;
93
94    ast_lowering::lower(compiler.gcx_mut());
95
96    Ok(ControlFlow::Continue(()))
97}
98
99#[instrument(level = "debug", skip_all)]
100fn analysis(gcx: Gcx<'_>) -> Result<ControlFlow<()>> {
101    if let ControlFlow::Break(()) = gcx.advance_stage(CompilerStage::Analysis) {
102        return Ok(ControlFlow::Break(()));
103    }
104
105    if let Some(dump) = &gcx.sess.opts.unstable.dump
106        && dump.kind.is_hir()
107    {
108        dump_hir(gcx, dump.paths.as_deref())?;
109    }
110
111    // Lower HIR types.
112    gcx.hir.par_item_ids().for_each(|id| {
113        let _ = gcx.type_of_item(id);
114        match id {
115            hir::ItemId::Struct(id) => _ = gcx.struct_field_types(id),
116            hir::ItemId::Contract(id) => _ = gcx.interface_functions(id),
117            _ => {}
118        }
119    });
120    gcx.sess.dcx.has_errors()?;
121
122    typeck::check(gcx);
123    gcx.sess.dcx.has_errors()?;
124
125    if !gcx.sess.opts.emit.is_empty() {
126        emit::emit(gcx);
127        gcx.sess.dcx.has_errors()?;
128    }
129
130    Ok(ControlFlow::Continue(()))
131}
132
133fn dump_ast(sess: &Session, sources: &Sources<'_>, paths: Option<&[String]>) -> Result<()> {
134    if let Some(paths) = paths {
135        for path in paths {
136            if let Some(source) = sources.iter().find(|&s| match_file_name(&s.file.name, path)) {
137                println!("{source:#?}");
138            } else {
139                let msg = format!("`-Zdump=ast={path:?}` did not match any source file");
140                let note = format!(
141                    "available source files: {}",
142                    sources
143                        .iter()
144                        .map(|s| s.file.name.display().to_string())
145                        .collect::<Vec<_>>()
146                        .join(", ")
147                );
148                return Err(sess.dcx.err(msg).note(note).emit());
149            }
150        }
151    } else {
152        println!("{sources:#?}");
153    }
154
155    Ok(())
156}
157
158fn dump_hir(gcx: Gcx<'_>, paths: Option<&[String]>) -> Result<()> {
159    println!("{:#?}", gcx.hir);
160    if let Some(paths) = paths {
161        println!("\nPaths not yet implemented: {paths:#?}");
162    }
163    Ok(())
164}
165
166fn match_file_name(name: &solar_interface::source_map::FileName, path: &str) -> bool {
167    match name {
168        solar_interface::source_map::FileName::Real(path_buf) => {
169            path_buf.as_os_str() == path || path_buf.file_stem() == Some(path.as_ref())
170        }
171        solar_interface::source_map::FileName::Stdin => path == "stdin" || path == "<stdin>",
172        solar_interface::source_map::FileName::Custom(name) => path == name,
173    }
174}
175
176fn fmt_bytes(bytes: usize) -> impl std::fmt::Display {
177    solar_data_structures::fmt::from_fn(move |f| {
178        let mut size = bytes as f64;
179        let mut suffix = "B";
180        if size >= 1024.0 {
181            size /= 1024.0;
182            suffix = "KiB";
183        }
184        if size >= 1024.0 {
185            size /= 1024.0;
186            suffix = "MiB";
187        }
188        if size >= 1024.0 {
189            size /= 1024.0;
190            suffix = "GiB";
191        }
192
193        let precision = if size.fract() != 0.0 { 2 } else { 0 };
194        write!(f, "{size:.precision$} {suffix}")?;
195        if suffix != "B" {
196            write!(f, " ({bytes} B)")?;
197        }
198        Ok(())
199    })
200}