Skip to main content

solar_interface/
session.rs

1use crate::{
2    ByteSymbol, ColorChoice, SessionGlobals, SourceMap, Symbol,
3    diagnostics::{DiagCtxt, EmittedDiagnostics},
4};
5use solar_config::{
6    CompileOpts, CompilerOutput, CompilerStage, SINGLE_THREADED_TARGET, UnstableOpts,
7};
8use std::{
9    fmt,
10    path::Path,
11    sync::{Arc, OnceLock},
12};
13
14/// Information about the current compiler session.
15pub struct Session {
16    /// The compiler options.
17    pub opts: CompileOpts,
18
19    /// The diagnostics context.
20    pub dcx: DiagCtxt,
21    /// The globals.
22    globals: Arc<SessionGlobals>,
23    /// The rayon thread pool. This is spawned lazily on first use, rather than always constructing
24    /// one with `SessionBuilder`.
25    thread_pool: OnceLock<rayon::ThreadPool>,
26}
27
28impl Default for Session {
29    fn default() -> Self {
30        Self::new(CompileOpts::default())
31    }
32}
33
34impl fmt::Debug for Session {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        f.debug_struct("Session")
37            .field("opts", &self.opts)
38            .field("dcx", &self.dcx)
39            .finish_non_exhaustive()
40    }
41}
42
43/// [`Session`] builder.
44#[derive(Default)]
45#[must_use = "builders don't do anything unless you call `build`"]
46pub struct SessionBuilder {
47    dcx: Option<DiagCtxt>,
48    globals: Option<SessionGlobals>,
49    opts: Option<CompileOpts>,
50}
51
52impl SessionBuilder {
53    /// Sets the diagnostic context.
54    ///
55    /// If `opts` is set this will default to [`DiagCtxt::from_opts`], otherwise this is required.
56    ///
57    /// See also the `with_*_emitter*` methods.
58    pub fn dcx(mut self, dcx: DiagCtxt) -> Self {
59        self.dcx = Some(dcx);
60        self
61    }
62
63    /// Sets the source map.
64    pub fn source_map(mut self, source_map: Arc<SourceMap>) -> Self {
65        self.get_globals().source_map = source_map;
66        self
67    }
68
69    /// Sets the compiler options.
70    pub fn opts(mut self, opts: CompileOpts) -> Self {
71        self.opts = Some(opts);
72        self
73    }
74
75    /// Sets the diagnostic context to a test emitter.
76    #[inline]
77    pub fn with_test_emitter(mut self) -> Self {
78        let sm = self.get_source_map();
79        self.dcx(DiagCtxt::with_test_emitter(Some(sm)))
80    }
81
82    /// Sets the diagnostic context to a stderr emitter.
83    #[inline]
84    pub fn with_stderr_emitter(self) -> Self {
85        self.with_stderr_emitter_and_color(ColorChoice::Auto)
86    }
87
88    /// Sets the diagnostic context to a stderr emitter and a color choice.
89    #[inline]
90    pub fn with_stderr_emitter_and_color(mut self, color_choice: ColorChoice) -> Self {
91        let sm = self.get_source_map();
92        self.dcx(DiagCtxt::with_stderr_emitter_and_color(Some(sm), color_choice))
93    }
94
95    /// Sets the diagnostic context to a human emitter that emits diagnostics to a local buffer.
96    #[inline]
97    pub fn with_buffer_emitter(mut self, color_choice: ColorChoice) -> Self {
98        let sm = self.get_source_map();
99        self.dcx(DiagCtxt::with_buffer_emitter(Some(sm), color_choice))
100    }
101
102    /// Sets the diagnostic context to a silent emitter.
103    #[inline]
104    pub fn with_silent_emitter(self, fatal_note: Option<String>) -> Self {
105        self.dcx(DiagCtxt::with_silent_emitter(fatal_note))
106    }
107
108    /// Sets the number of threads to use for parallelism to 1.
109    #[inline]
110    pub fn single_threaded(self) -> Self {
111        self.threads(1)
112    }
113
114    /// Sets the number of threads to use for parallelism. Zero specifies the number of logical
115    /// cores.
116    #[inline]
117    pub fn threads(mut self, threads: usize) -> Self {
118        self.opts_mut().threads = threads.into();
119        self
120    }
121
122    /// Gets the source map from the diagnostics context.
123    fn get_source_map(&mut self) -> Arc<SourceMap> {
124        self.get_globals().source_map.clone()
125    }
126
127    fn get_globals(&mut self) -> &mut SessionGlobals {
128        self.globals.get_or_insert_default()
129    }
130
131    /// Returns a mutable reference to the options.
132    fn opts_mut(&mut self) -> &mut CompileOpts {
133        self.opts.get_or_insert_default()
134    }
135
136    /// Consumes the builder to create a new session.
137    ///
138    /// The diagnostics context must be set before calling this method, either by calling
139    /// [`dcx`](Self::dcx) or by using one of the provided helper methods, like
140    /// [`with_stderr_emitter`](Self::with_stderr_emitter).
141    ///
142    /// # Panics
143    ///
144    /// Panics if:
145    /// - the diagnostics context is not set
146    /// - the source map in the diagnostics context does not match the one set in the builder
147    #[track_caller]
148    pub fn build(mut self) -> Session {
149        let opts = self.opts.take();
150        let mut dcx = self.dcx.take().unwrap_or_else(|| {
151            opts.as_ref()
152                .map(DiagCtxt::from_opts)
153                .unwrap_or_else(|| panic!("either diagnostics context or options must be set"))
154        });
155        let sess = Session {
156            globals: Arc::new(match self.globals.take() {
157                Some(globals) => {
158                    // Check that the source map matches the one in the diagnostics context.
159                    if let Some(sm) = dcx.source_map_mut() {
160                        assert!(
161                            Arc::ptr_eq(&globals.source_map, sm),
162                            "session source map does not match the one in the diagnostics context"
163                        );
164                    }
165                    globals
166                }
167                None => {
168                    // Set the source map from the diagnostics context.
169                    let sm = dcx.source_map_mut().cloned().unwrap_or_default();
170                    SessionGlobals::new(sm)
171                }
172            }),
173            dcx,
174            opts: opts.unwrap_or_default(),
175            thread_pool: OnceLock::new(),
176        };
177        sess.reconfigure();
178        debug!(version = %solar_config::version::SEMVER_VERSION, "created new session");
179        sess
180    }
181}
182
183impl Session {
184    /// Creates a new session builder.
185    #[inline]
186    pub fn builder() -> SessionBuilder {
187        SessionBuilder::default()
188    }
189
190    /// Creates a new session from the given options.
191    ///
192    /// See [`builder`](Self::builder) for a more flexible way to create a session.
193    pub fn new(opts: CompileOpts) -> Self {
194        Self::builder().opts(opts).build()
195    }
196
197    /// Infers the language from the input files.
198    pub fn infer_language(&mut self) {
199        if !self.opts.input.is_empty()
200            && self.opts.input.iter().all(|arg| Path::new(arg).extension() == Some("yul".as_ref()))
201        {
202            self.opts.language = solar_config::Language::Yul;
203        }
204    }
205
206    /// Validates the session options.
207    pub fn validate(&self) -> crate::Result<()> {
208        let mut result = Ok(());
209        result = result.and(self.check_unique("emit", &self.opts.emit));
210        result
211    }
212
213    /// Reconfigures inner state to match any new options.
214    ///
215    /// Call this after updating options.
216    pub fn reconfigure(&self) {
217        'bp: {
218            let new_base_path = if self.opts.unstable.ui_testing {
219                // `ui_test` relies on absolute paths.
220                None
221            } else if let Some(base_path) =
222                self.opts.base_path.clone().or_else(|| std::env::current_dir().ok())
223                && let Ok(base_path) = self.source_map().file_loader().canonicalize_path(&base_path)
224            {
225                Some(base_path)
226            } else {
227                break 'bp;
228            };
229            self.source_map().set_base_path(new_base_path);
230        }
231    }
232
233    fn check_unique<T: Eq + std::hash::Hash + std::fmt::Display>(
234        &self,
235        name: &str,
236        list: &[T],
237    ) -> crate::Result<()> {
238        let mut result = Ok(());
239        let mut seen = std::collections::HashSet::new();
240        for item in list {
241            if !seen.insert(item) {
242                let msg = format!("cannot specify `--{name} {item}` twice");
243                result = Err(self.dcx.err(msg).emit());
244            }
245        }
246        result
247    }
248
249    /// Returns the unstable options.
250    #[inline]
251    pub fn unstable(&self) -> &UnstableOpts {
252        &self.opts.unstable
253    }
254
255    /// Returns the emitted diagnostics as a result. Can be empty.
256    ///
257    /// Returns `None` if the underlying emitter is not a human buffer emitter created with
258    /// [`with_buffer_emitter`](SessionBuilder::with_buffer_emitter).
259    ///
260    /// Results `Ok` if there are no errors, `Err` otherwise.
261    ///
262    /// # Examples
263    ///
264    /// Print diagnostics to `stdout` if there are no errors, otherwise propagate with `?`:
265    ///
266    /// ```no_run
267    /// # fn f(dcx: solar_interface::diagnostics::DiagCtxt) -> Result<(), Box<dyn std::error::Error>> {
268    /// println!("{}", dcx.emitted_diagnostics_result().unwrap()?);
269    /// # Ok(())
270    /// # }
271    /// ```
272    #[inline]
273    pub fn emitted_diagnostics_result(
274        &self,
275    ) -> Option<Result<EmittedDiagnostics, EmittedDiagnostics>> {
276        self.dcx.emitted_diagnostics_result()
277    }
278
279    /// Returns the emitted diagnostics. Can be empty.
280    ///
281    /// Returns `None` if the underlying emitter is not a human buffer emitter created with
282    /// [`with_buffer_emitter`](SessionBuilder::with_buffer_emitter).
283    #[inline]
284    pub fn emitted_diagnostics(&self) -> Option<EmittedDiagnostics> {
285        self.dcx.emitted_diagnostics()
286    }
287
288    /// Returns `Err` with the printed diagnostics if any errors have been emitted.
289    ///
290    /// Returns `None` if the underlying emitter is not a human buffer emitter created with
291    /// [`with_buffer_emitter`](SessionBuilder::with_buffer_emitter).
292    #[inline]
293    pub fn emitted_errors(&self) -> Option<Result<(), EmittedDiagnostics>> {
294        self.dcx.emitted_errors()
295    }
296
297    /// Returns a reference to the source map.
298    #[inline]
299    pub fn source_map(&self) -> &SourceMap {
300        &self.globals.source_map
301    }
302
303    /// Clones the source map.
304    #[inline]
305    pub fn clone_source_map(&self) -> Arc<SourceMap> {
306        self.globals.source_map.clone()
307    }
308
309    /// Returns `true` if compilation should stop after the given stage.
310    #[inline]
311    pub fn stop_after(&self, stage: CompilerStage) -> bool {
312        self.opts.stop_after >= Some(stage)
313    }
314
315    /// Returns the number of threads to use for parallelism.
316    #[inline]
317    pub fn threads(&self) -> usize {
318        self.opts.threads().get()
319    }
320
321    /// Returns `true` if parallelism is not enabled.
322    #[inline]
323    pub fn is_sequential(&self) -> bool {
324        self.threads() == 1
325    }
326
327    /// Returns `true` if parallelism is enabled.
328    #[inline]
329    pub fn is_parallel(&self) -> bool {
330        !self.is_sequential()
331    }
332
333    /// Returns `true` if the given output should be emitted.
334    #[inline]
335    pub fn do_emit(&self, output: CompilerOutput) -> bool {
336        self.opts.emit.contains(&output)
337    }
338
339    /// Spawns the given closure on the thread pool or executes it immediately if parallelism is not
340    /// enabled.
341    ///
342    /// NOTE: on a `use_current_thread` thread pool `rayon::spawn` will never execute without
343    /// yielding to rayon, so prefer using this method over `rayon::spawn`.
344    #[inline]
345    pub fn spawn(&self, f: impl FnOnce() + Send + 'static) {
346        if self.is_sequential() {
347            f();
348        } else {
349            rayon::spawn(f);
350        }
351    }
352
353    /// Takes two closures and potentially runs them in parallel. It returns a pair of the results
354    /// from those closures.
355    ///
356    /// NOTE: on a `use_current_thread` thread pool `rayon::join` will never execute without
357    /// yielding to rayon, so prefer using this method over `rayon::join`.
358    #[inline]
359    pub fn join<A, B, RA, RB>(&self, oper_a: A, oper_b: B) -> (RA, RB)
360    where
361        A: FnOnce() -> RA + Send,
362        B: FnOnce() -> RB + Send,
363        RA: Send,
364        RB: Send,
365    {
366        if self.is_sequential() { (oper_a(), oper_b()) } else { rayon::join(oper_a, oper_b) }
367    }
368
369    /// Executes the given closure in a fork-join scope.
370    ///
371    /// See [`rayon::scope`] for more details.
372    #[inline]
373    pub fn scope<'scope, OP, R>(&self, op: OP) -> R
374    where
375        OP: FnOnce(solar_data_structures::sync::Scope<'_, 'scope>) -> R + Send,
376        R: Send,
377    {
378        solar_data_structures::sync::scope(self.is_parallel(), op)
379    }
380
381    /// Sets up the session globals and executes the given closure in the thread pool.
382    ///
383    /// The thread pool and globals are stored in this [`Session`] itself, meaning multiple
384    /// consecutive calls to [`enter`](Self::enter) will share the same globals and resources.
385    #[track_caller]
386    pub fn enter<R: Send>(&self, f: impl FnOnce() -> R + Send) -> R {
387        if in_rayon() {
388            // Avoid panicking if we were to build a `current_thread` thread pool.
389            if self.is_sequential() {
390                reentrant_log();
391                return self.enter_sequential(f);
392            }
393            // Avoid creating a new thread pool if it's already set up with the same globals.
394            if self.is_entered() {
395                // No need to set again.
396                return f();
397            }
398        }
399
400        self.enter_sequential(|| self.thread_pool().install(f))
401    }
402
403    /// Sets up the session globals and executes the given closure in the current thread.
404    ///
405    /// Note that this does not set up the rayon thread pool. This is only useful when parsing
406    /// sequentially, like manually using `Parser`. Otherwise, it might cause panics later on if a
407    /// thread pool is expected to be set up correctly.
408    ///
409    /// See [`enter`](Self::enter) for more details.
410    #[inline]
411    #[track_caller]
412    pub fn enter_sequential<R>(&self, f: impl FnOnce() -> R) -> R {
413        self.globals.set(f)
414    }
415
416    /// Interns a string in this session's symbol interner.
417    ///
418    /// The symbol may not be usable on its own if the session has not been [entered](Self::enter).
419    #[inline]
420    #[cfg_attr(debug_assertions, track_caller)]
421    pub fn intern(&self, s: &str) -> Symbol {
422        self.globals.symbol_interner.intern(s)
423    }
424
425    /// Resolves a symbol to its string representation.
426    ///
427    /// The given symbol must have been interned in this session.
428    #[inline]
429    #[cfg_attr(debug_assertions, track_caller)]
430    pub fn resolve_symbol(&self, s: Symbol) -> &str {
431        self.globals.symbol_interner.get(s)
432    }
433
434    /// Interns a byte string in this session's symbol interner.
435    ///
436    /// The symbol may not be usable on its own if the session has not been [entered](Self::enter).
437    #[inline]
438    #[cfg_attr(debug_assertions, track_caller)]
439    pub fn intern_byte_str(&self, s: &[u8]) -> ByteSymbol {
440        self.globals.symbol_interner.intern_byte_str(s)
441    }
442
443    /// Resolves a byte symbol to its string representation.
444    ///
445    /// The given symbol must have been interned in this session.
446    #[inline]
447    #[cfg_attr(debug_assertions, track_caller)]
448    pub fn resolve_byte_str(&self, s: ByteSymbol) -> &[u8] {
449        self.globals.symbol_interner.get_byte_str(s)
450    }
451
452    /// Returns `true` if this session has been entered.
453    pub fn is_entered(&self) -> bool {
454        SessionGlobals::try_with(|g| g.is_some_and(|g| g.maybe_eq(&self.globals)))
455    }
456
457    fn thread_pool(&self) -> &rayon::ThreadPool {
458        self.thread_pool.get_or_init(|| {
459            trace!(threads = self.threads(), "building rayon thread pool");
460            self.thread_pool_builder()
461                .spawn_handler(|thread| {
462                    let mut builder = std::thread::Builder::new();
463                    if let Some(name) = thread.name() {
464                        builder = builder.name(name.to_string());
465                    }
466                    if let Some(size) = thread.stack_size() {
467                        builder = builder.stack_size(size);
468                    }
469                    let globals = self.globals.clone();
470                    builder.spawn(move || globals.set(|| thread.run()))?;
471                    Ok(())
472                })
473                .build()
474                .unwrap_or_else(|e| self.handle_thread_pool_build_error(e))
475        })
476    }
477
478    fn thread_pool_builder(&self) -> rayon::ThreadPoolBuilder {
479        let threads = self.threads();
480        debug_assert!(threads > 0, "number of threads must already be resolved");
481        let mut builder = rayon::ThreadPoolBuilder::new()
482            .thread_name(|i| format!("solar-{i}"))
483            .num_threads(threads);
484        // We still want to use a rayon thread pool with 1 thread so that `ParallelIterator`s don't
485        // install and run in the default global thread pool.
486        if threads == 1 {
487            builder = builder.use_current_thread();
488        }
489        builder
490    }
491
492    #[cold]
493    fn handle_thread_pool_build_error(&self, e: rayon::ThreadPoolBuildError) -> ! {
494        let mut err = self.dcx.fatal(format!("failed to build the rayon thread pool: {e}"));
495        if self.is_parallel() {
496            if SINGLE_THREADED_TARGET {
497                err = err.note("the current target might not support multi-threaded execution");
498            }
499            err = err.help("try running with `--threads 1` / `-j1` to disable parallelism");
500        }
501        err.emit()
502    }
503}
504
505fn reentrant_log() {
506    debug!(
507        "running in the current thread's rayon thread pool; \
508         this could cause panics later on if it was created without setting the session globals!"
509    );
510}
511
512#[inline]
513fn in_rayon() -> bool {
514    rayon::current_thread_index().is_some()
515}
516
517#[cfg(test)]
518mod tests {
519    use super::*;
520    use std::path::PathBuf;
521
522    /// Session to test `enter`.
523    fn enter_tests_session() -> Session {
524        let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
525        sess.source_map().new_source_file(PathBuf::from("test"), "abcd").unwrap();
526        sess
527    }
528
529    #[track_caller]
530    fn use_globals_parallel(sess: &Session) {
531        use rayon::prelude::*;
532
533        use_globals();
534        sess.spawn(|| use_globals());
535        sess.join(|| use_globals(), || use_globals());
536        [1, 2, 3].par_iter().for_each(|_| use_globals());
537        use_globals();
538    }
539
540    #[track_caller]
541    fn use_globals() {
542        use_globals_no_sm();
543
544        let span = crate::Span::new(crate::BytePos(0), crate::BytePos(1));
545        assert_eq!(format!("{span:?}"), "test:1:1: 1:2");
546        assert_eq!(format!("{span:#?}"), "test:1:1: 1:2");
547    }
548
549    #[track_caller]
550    fn use_globals_no_sm() {
551        SessionGlobals::with(|_globals| {});
552
553        let s = "hello";
554        let sym = crate::Symbol::intern(s);
555        assert_eq!(sym.as_str(), s);
556    }
557
558    #[track_caller]
559    fn cant_use_globals() {
560        std::panic::catch_unwind(|| use_globals()).unwrap_err();
561    }
562
563    #[test]
564    fn builder() {
565        let _ = Session::builder().with_stderr_emitter().build();
566    }
567
568    #[test]
569    fn not_builder() {
570        let _ = Session::new(CompileOpts::default());
571        let _ = Session::default();
572    }
573
574    #[test]
575    #[should_panic = "session source map does not match the one in the diagnostics context"]
576    fn sm_mismatch() {
577        let sm1 = Arc::<SourceMap>::default();
578        let sm2 = Arc::<SourceMap>::default();
579        assert!(!Arc::ptr_eq(&sm1, &sm2));
580        Session::builder().source_map(sm1).dcx(DiagCtxt::with_stderr_emitter(Some(sm2))).build();
581    }
582
583    #[test]
584    #[should_panic = "either diagnostics context or options must be set"]
585    fn no_dcx() {
586        Session::builder().build();
587    }
588
589    #[test]
590    fn dcx() {
591        let _ = Session::builder().dcx(DiagCtxt::with_stderr_emitter(None)).build();
592        let _ =
593            Session::builder().dcx(DiagCtxt::with_stderr_emitter(Some(Default::default()))).build();
594    }
595
596    #[test]
597    fn local() {
598        let sess = Session::builder().with_stderr_emitter().build();
599        assert!(sess.emitted_diagnostics().is_none());
600        assert!(sess.emitted_errors().is_none());
601
602        let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
603        sess.dcx.err("test").emit();
604        let err = sess.dcx.emitted_errors().unwrap().unwrap_err();
605        let err = Box::new(err) as Box<dyn std::error::Error>;
606        assert!(err.to_string().contains("error: test"), "{err:?}");
607    }
608
609    #[test]
610    fn enter() {
611        crate::enter(|| {
612            use_globals_no_sm();
613            cant_use_globals();
614        });
615
616        let sess = enter_tests_session();
617        sess.enter(|| use_globals());
618        assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
619        assert!(sess.dcx.emitted_errors().unwrap().is_ok());
620        sess.enter(|| {
621            use_globals();
622            sess.enter(|| use_globals());
623            use_globals();
624        });
625        assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
626        assert!(sess.dcx.emitted_errors().unwrap().is_ok());
627
628        sess.enter_sequential(|| {
629            use_globals();
630            sess.enter(|| {
631                use_globals_parallel(&sess);
632                sess.enter(|| use_globals_parallel(&sess));
633                use_globals_parallel(&sess);
634            });
635            use_globals();
636        });
637        assert!(sess.dcx.emitted_diagnostics().unwrap().is_empty());
638        assert!(sess.dcx.emitted_errors().unwrap().is_ok());
639    }
640
641    #[test]
642    fn enter_diags() {
643        let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
644        assert!(sess.dcx.emitted_errors().unwrap().is_ok());
645        sess.enter(|| {
646            sess.dcx.err("test1").emit();
647            assert!(sess.dcx.emitted_errors().unwrap().is_err());
648        });
649        assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
650        sess.enter(|| {
651            sess.dcx.err("test2").emit();
652            assert!(sess.dcx.emitted_errors().unwrap().is_err());
653        });
654        assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test1"));
655        assert!(sess.dcx.emitted_errors().unwrap().unwrap_err().to_string().contains("test2"));
656    }
657
658    #[test]
659    fn enter_thread_pool() {
660        let sess = enter_tests_session();
661
662        assert!(!in_rayon());
663        sess.enter(|| {
664            assert!(in_rayon());
665            sess.enter(|| {
666                assert!(in_rayon());
667            });
668            assert!(in_rayon());
669        });
670        sess.enter_sequential(|| {
671            assert!(!in_rayon());
672            sess.enter(|| {
673                assert!(in_rayon());
674            });
675            assert!(!in_rayon());
676            sess.enter_sequential(|| {
677                assert!(!in_rayon());
678            });
679            assert!(!in_rayon());
680        });
681        assert!(!in_rayon());
682
683        let pool = rayon::ThreadPoolBuilder::new().build().unwrap();
684        pool.install(|| {
685            assert!(in_rayon());
686            cant_use_globals();
687            sess.enter(|| use_globals_parallel(&sess));
688            assert!(in_rayon());
689            cant_use_globals();
690        });
691        assert!(!in_rayon());
692    }
693
694    #[test]
695    fn enter_different_nested_sessions() {
696        let sess1 = enter_tests_session();
697        let sess2 = enter_tests_session();
698        assert!(!sess1.globals.maybe_eq(&sess2.globals));
699        sess1.enter(|| {
700            SessionGlobals::with(|g| assert!(g.maybe_eq(&sess1.globals)));
701            use_globals();
702            sess2.enter(|| {
703                SessionGlobals::with(|g| assert!(g.maybe_eq(&sess2.globals)));
704                use_globals();
705            });
706            use_globals();
707        });
708    }
709
710    #[test]
711    fn set_opts() {
712        let _ = Session::builder()
713            .with_test_emitter()
714            .opts(CompileOpts {
715                evm_version: solar_config::EvmVersion::Berlin,
716                unstable: UnstableOpts { ast_stats: false, ..Default::default() },
717                ..Default::default()
718            })
719            .build();
720    }
721}