solar_sema/ty/
mod.rs

1use crate::{
2    Source, Sources, ast,
3    ast_lowering::SymbolResolver,
4    builtins::{Builtin, members},
5    hir::{self, Hir, SourceId},
6};
7use alloy_primitives::{B256, Selector, U256, keccak256};
8use either::Either;
9use solar_ast::{DataLocation, StateMutability, TypeSize, Visibility};
10use solar_data_structures::{
11    BumpExt,
12    fmt::{from_fn, or_list},
13    map::{FxBuildHasher, FxHashMap, FxHashSet},
14    smallvec::SmallVec,
15    trustme,
16};
17use solar_interface::{
18    Ident, Session, Span,
19    config::CompilerStage,
20    diagnostics::{DiagCtxt, ErrorGuaranteed},
21    source_map::{FileName, SourceFile},
22};
23use std::{
24    fmt,
25    hash::Hash,
26    ops::ControlFlow,
27    sync::{
28        Arc,
29        atomic::{AtomicUsize, Ordering},
30    },
31};
32use thread_local::ThreadLocal;
33
34mod abi;
35pub use abi::{TyAbiPrinter, TyAbiPrinterMode};
36
37mod common;
38pub use common::{CommonTypes, EachDataLoc};
39
40mod interner;
41use interner::Interner;
42
43#[allow(clippy::module_inception)]
44mod ty;
45pub use ty::{Ty, TyData, TyFlags, TyFnPtr, TyKind};
46
47type FxOnceMap<K, V> = once_map::OnceMap<K, V, FxBuildHasher>;
48
49/// A function exported by a contract.
50#[derive(Clone, Copy, Debug)]
51pub struct InterfaceFunction<'gcx> {
52    /// The function ID.
53    pub id: hir::FunctionId,
54    /// The function 4-byte selector.
55    pub selector: Selector,
56    /// The function type. This is always a function pointer.
57    pub ty: Ty<'gcx>,
58}
59
60/// List of all the functions exported by a contract.
61///
62/// Return type of [`Gcx::interface_functions`].
63#[derive(Clone, Copy, Debug)]
64pub struct InterfaceFunctions<'gcx> {
65    /// The exported functions along with their selector.
66    pub functions: &'gcx [InterfaceFunction<'gcx>],
67    /// The index in `functions` where the inherited functions start.
68    pub inheritance_start: usize,
69}
70
71impl<'gcx> InterfaceFunctions<'gcx> {
72    /// Returns all the functions.
73    pub fn all(&self) -> &'gcx [InterfaceFunction<'gcx>] {
74        self.functions
75    }
76
77    /// Returns the defined functions.
78    pub fn own(&self) -> &'gcx [InterfaceFunction<'gcx>] {
79        &self.functions[..self.inheritance_start]
80    }
81
82    /// Returns the inherited functions.
83    pub fn inherited(&self) -> &'gcx [InterfaceFunction<'gcx>] {
84        &self.functions[self.inheritance_start..]
85    }
86}
87
88impl<'gcx> std::ops::Deref for InterfaceFunctions<'gcx> {
89    type Target = &'gcx [InterfaceFunction<'gcx>];
90
91    #[inline]
92    fn deref(&self) -> &Self::Target {
93        &self.functions
94    }
95}
96
97impl<'gcx> IntoIterator for InterfaceFunctions<'gcx> {
98    type Item = &'gcx InterfaceFunction<'gcx>;
99    type IntoIter = std::slice::Iter<'gcx, InterfaceFunction<'gcx>>;
100
101    #[inline]
102    fn into_iter(self) -> Self::IntoIter {
103        self.functions.iter()
104    }
105}
106
107/// Recursiveness of a type.
108#[derive(Clone, Copy, Debug)]
109pub enum Recursiveness {
110    /// Not recursive.
111    None,
112    /// Recursive through indirection.
113    Recursive,
114    /// Recursive through direct reference. An error has already been emitted.
115    Infinite(ErrorGuaranteed),
116}
117
118impl Recursiveness {
119    /// Returns `true` if the type is not recursive.
120    #[inline]
121    pub fn is_none(self) -> bool {
122        matches!(self, Self::None)
123    }
124
125    /// Returns `true` if the type is recursive.
126    #[inline]
127    pub fn is_recursive(self) -> bool {
128        !self.is_none()
129    }
130}
131
132/// Reference to the [global context](GlobalCtxt).
133#[derive(Clone, Copy)]
134#[cfg_attr(feature = "nightly", rustc_pass_by_value)]
135pub struct Gcx<'gcx>(&'gcx GlobalCtxt<'gcx>);
136
137impl<'gcx> std::ops::Deref for Gcx<'gcx> {
138    type Target = &'gcx GlobalCtxt<'gcx>;
139
140    #[inline(always)]
141    fn deref(&self) -> &Self::Target {
142        &self.0
143    }
144}
145
146impl<'gcx> fmt::Debug for Gcx<'gcx> {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        self.0.fmt(f)
149    }
150}
151
152/// Transparent wrapper around `&'gcx mut GlobalCtxt<'gcx>`.
153///
154/// This uses a raw pointer because using `&mut` directly would make `'gcx` covariant and this just
155/// is too annoying/impossible to deal with.
156/// Since it's only used internally (`pub(crate)`), this is fine.
157#[repr(transparent)]
158pub(crate) struct GcxMut<'gcx>(*mut GlobalCtxt<'gcx>);
159
160impl<'gcx> GcxMut<'gcx> {
161    #[inline(always)]
162    pub(crate) fn new(gcx: &mut GlobalCtxt<'gcx>) -> Self {
163        Self(gcx)
164    }
165
166    #[inline(always)]
167    pub(crate) fn get(&self) -> Gcx<'gcx> {
168        unsafe { Gcx(&*self.0) }
169    }
170
171    #[inline(always)]
172    pub(crate) fn get_mut(&mut self) -> &'gcx mut GlobalCtxt<'gcx> {
173        unsafe { &mut *self.0 }
174    }
175}
176
177impl<'gcx> std::ops::Deref for GcxMut<'gcx> {
178    type Target = &'gcx mut GlobalCtxt<'gcx>;
179
180    #[inline(always)]
181    fn deref(&self) -> &Self::Target {
182        unsafe { core::mem::transmute(self) }
183    }
184}
185
186impl<'gcx> std::ops::DerefMut for GcxMut<'gcx> {
187    #[inline(always)]
188    fn deref_mut(&mut self) -> &mut Self::Target {
189        unsafe { core::mem::transmute(self) }
190    }
191}
192
193#[cfg(test)]
194fn _gcx_traits() {
195    fn assert_send_sync<T: Send + Sync>() {}
196    assert_send_sync::<Gcx<'static>>();
197}
198
199struct AtomicCompilerStage(AtomicUsize);
200
201impl AtomicCompilerStage {
202    fn new() -> Self {
203        Self(AtomicUsize::new(usize::MAX))
204    }
205
206    fn set(&self, stage: CompilerStage) {
207        self.0.store(stage as usize, Ordering::Relaxed);
208    }
209
210    fn get(&self) -> Option<CompilerStage> {
211        let stage = self.0.load(Ordering::Relaxed);
212        if stage == usize::MAX { None } else { Some(CompilerStage::from_repr(stage).unwrap()) }
213    }
214}
215
216/// The global compilation context.
217pub struct GlobalCtxt<'gcx> {
218    pub sess: &'gcx Session,
219    pub sources: Sources<'gcx>,
220    pub(crate) symbol_resolver: SymbolResolver<'gcx>,
221    pub hir: Hir<'gcx>,
222    stage: AtomicCompilerStage,
223
224    pub types: CommonTypes<'gcx>,
225
226    pub(crate) ast_arenas: ThreadLocal<ast::Arena>,
227    pub(crate) hir_arenas: ThreadLocal<hir::Arena>,
228    interner: Interner<'gcx>,
229    cache: Cache<'gcx>,
230}
231
232impl fmt::Debug for GlobalCtxt<'_> {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        f.debug_struct("GlobalCtxt")
235            .field("stage", &self.stage.get())
236            .field("sess", self.sess)
237            .field("sources", &self.sources.len())
238            .finish_non_exhaustive()
239    }
240}
241
242impl<'gcx> GlobalCtxt<'gcx> {
243    pub(crate) fn new(sess: &'gcx Session) -> Self {
244        let interner = Interner::new();
245        let hir_arenas = ThreadLocal::<hir::Arena>::new();
246        Self {
247            sess,
248            sources: Sources::new(),
249            symbol_resolver: SymbolResolver::new(&sess.dcx),
250            hir: Hir::new(),
251            stage: AtomicCompilerStage::new(),
252
253            // SAFETY: stable address because ThreadLocal holds the arenas through indirection.
254            types: CommonTypes::new(
255                &interner,
256                unsafe { trustme::decouple_lt(&hir_arenas) }.get_or_default().bump(),
257            ),
258
259            ast_arenas: ThreadLocal::new(),
260            hir_arenas,
261            interner,
262            cache: Cache::default(),
263        }
264    }
265}
266
267impl<'gcx> Gcx<'gcx> {
268    pub(crate) fn new(gcx: &'gcx GlobalCtxt<'gcx>) -> Self {
269        Self(gcx)
270    }
271
272    /// Returns the current compiler stage.
273    pub fn stage(&self) -> Option<CompilerStage> {
274        self.stage.get()
275    }
276
277    pub(crate) fn advance_stage(&self, to: CompilerStage) -> ControlFlow<()> {
278        let from = self.stage();
279        let result = self.advance_stage_(to);
280        trace!(?from, ?to, ?result, "advance stage");
281        result
282    }
283
284    fn advance_stage_(&self, to: CompilerStage) -> ControlFlow<()> {
285        let current = self.stage();
286
287        // Special case: allow calling `parse` multiple times while currently parsing.
288        if to == CompilerStage::Parsing && current == Some(to) {
289            return ControlFlow::Continue(());
290        }
291
292        let next = CompilerStage::next_opt(current);
293        if next.is_none_or(|next| to != next) {
294            let current_s = match current {
295                Some(s) => s.to_str(),
296                None => "none",
297            };
298            let next_s = match next {
299                Some(s) => &format!("`{s}`"),
300                None => "none (current stage is the last)",
301            };
302            self.dcx()
303                .bug(format!(
304                    "invalid compiler stage transition: cannot advance from `{current_s}` to `{to}`"
305                ))
306                .note(format!("expected next stage: {next_s}"))
307                .note("stages must be advanced sequentially")
308                .emit();
309        }
310
311        if let Some(current) = current
312            && self.sess.stop_after(current)
313        {
314            return ControlFlow::Break(());
315        }
316
317        self.stage.set(to);
318        ControlFlow::Continue(())
319    }
320
321    /// Returns the diagnostics context.
322    pub fn dcx(self) -> &'gcx DiagCtxt {
323        &self.sess.dcx
324    }
325
326    pub fn arena(self) -> &'gcx hir::Arena {
327        self.hir_arenas.get_or_default()
328    }
329
330    pub fn bump(self) -> &'gcx bumpalo::Bump {
331        self.arena().bump()
332    }
333
334    pub fn alloc<T>(self, value: T) -> &'gcx T {
335        self.bump().alloc(value)
336    }
337
338    pub fn mk_ty(self, kind: TyKind<'gcx>) -> Ty<'gcx> {
339        self.interner.intern_ty_with_flags(self.bump(), kind, |kind| TyFlags::calculate(self, kind))
340    }
341
342    pub fn mk_tys(self, tys: &[Ty<'gcx>]) -> &'gcx [Ty<'gcx>] {
343        self.interner.intern_tys(self.bump(), tys)
344    }
345
346    pub fn mk_ty_iter(self, tys: impl Iterator<Item = Ty<'gcx>>) -> &'gcx [Ty<'gcx>] {
347        self.interner.intern_ty_iter(self.bump(), tys)
348    }
349
350    fn mk_item_tys<T: Into<hir::ItemId> + Copy>(self, ids: &[T]) -> &'gcx [Ty<'gcx>] {
351        self.mk_ty_iter(ids.iter().map(|&id| self.type_of_item(id.into())))
352    }
353
354    pub fn mk_ty_string_literal(self, s: &[u8]) -> Ty<'gcx> {
355        self.mk_ty(TyKind::StringLiteral(
356            std::str::from_utf8(s).is_ok(),
357            TypeSize::new(s.len().min(32) as u8).unwrap(),
358        ))
359    }
360
361    pub fn mk_ty_int_literal(self, size: TypeSize) -> Ty<'gcx> {
362        self.mk_ty(TyKind::IntLiteral(size))
363    }
364
365    pub fn mk_ty_fn_ptr(self, ptr: TyFnPtr<'gcx>) -> Ty<'gcx> {
366        self.mk_ty(TyKind::FnPtr(self.interner.intern_ty_fn_ptr(self.bump(), ptr)))
367    }
368
369    pub fn mk_ty_fn(
370        self,
371        parameters: &[Ty<'gcx>],
372        state_mutability: StateMutability,
373        visibility: Visibility,
374        returns: &[Ty<'gcx>],
375    ) -> Ty<'gcx> {
376        self.mk_ty_fn_ptr(TyFnPtr {
377            parameters: self.mk_tys(parameters),
378            returns: self.mk_tys(returns),
379            state_mutability,
380            visibility,
381        })
382    }
383
384    pub(crate) fn mk_builtin_fn(
385        self,
386        parameters: &[Ty<'gcx>],
387        state_mutability: StateMutability,
388        returns: &[Ty<'gcx>],
389    ) -> Ty<'gcx> {
390        self.mk_ty_fn(parameters, state_mutability, Visibility::Internal, returns)
391    }
392
393    pub(crate) fn mk_builtin_mod(self, builtin: Builtin) -> Ty<'gcx> {
394        self.mk_ty(TyKind::BuiltinModule(builtin))
395    }
396
397    pub fn mk_ty_err(self, guar: ErrorGuaranteed) -> Ty<'gcx> {
398        Ty::new(self, TyKind::Err(guar))
399    }
400
401    /// Returns the source file with the given path, if it exists.
402    pub fn get_file(self, name: impl Into<FileName>) -> Option<Arc<SourceFile>> {
403        self.sess.source_map().get_file(name)
404    }
405
406    /// Returns the AST source at the given path, if it exists.
407    pub fn get_ast_source(
408        self,
409        name: impl Into<FileName>,
410    ) -> Option<(SourceId, &'gcx Source<'gcx>)> {
411        let file = self.get_file(name)?;
412        self.sources.get_file(&file)
413    }
414
415    /// Returns the HIR source at the given path, if it exists.
416    pub fn get_hir_source(
417        self,
418        name: impl Into<FileName>,
419    ) -> Option<(SourceId, &'gcx hir::Source<'gcx>)> {
420        let file = self.get_file(name)?;
421        self.hir.sources.iter_enumerated().find(|(_, source)| Arc::ptr_eq(&source.file, &file))
422    }
423
424    /// Returns the name of the given item.
425    ///
426    /// # Panics
427    ///
428    /// Panics if the item has no name, such as unnamed function parameters.
429    pub fn item_name(self, id: impl Into<hir::ItemId>) -> Ident {
430        let id = id.into();
431        self.item_name_opt(id).unwrap_or_else(|| panic!("item_name: missing name for item {id:?}"))
432    }
433
434    /// Returns the canonical name of the given item.
435    ///
436    /// This is the name of the item prefixed by the name of the contract it belongs to.
437    pub fn item_canonical_name(self, id: impl Into<hir::ItemId>) -> impl fmt::Display {
438        self.item_canonical_name_(id.into())
439    }
440    fn item_canonical_name_(self, id: hir::ItemId) -> impl fmt::Display {
441        let name = self.item_name(id);
442        let contract = self.hir.item(id).contract().map(|id| self.item_name(id));
443        from_fn(move |f| {
444            if let Some(contract) = contract {
445                write!(f, "{contract}.")?;
446            }
447            write!(f, "{name}")
448        })
449    }
450
451    /// Returns the fully qualified name of the contract.
452    pub fn contract_fully_qualified_name(
453        self,
454        id: hir::ContractId,
455    ) -> impl fmt::Display + use<'gcx> {
456        from_fn(move |f| {
457            let c = self.hir.contract(id);
458            let source = self.hir.source(c.source);
459            write!(f, "{}:{}", source.file.name.display(), c.name)
460        })
461    }
462
463    /// Returns an iterator over the fields of the given item.
464    ///
465    /// Accepts structs, functions, errors, and events.
466    pub fn item_fields(
467        self,
468        id: impl Into<hir::ItemId>,
469    ) -> impl Iterator<Item = (Ty<'gcx>, hir::VariableId)> {
470        self.item_fields_(id.into())
471    }
472
473    fn item_fields_(self, id: hir::ItemId) -> impl Iterator<Item = (Ty<'gcx>, hir::VariableId)> {
474        let tys = if let hir::ItemId::Struct(id) = id {
475            self.struct_field_types(id)
476        } else {
477            self.item_parameter_types(id)
478        };
479        let params = self.item_parameters(id);
480        debug_assert_eq!(tys.len(), params.len());
481        std::iter::zip(tys.iter().copied(), params.iter().copied())
482    }
483
484    /// Returns the parameter variable declarations of the given function-like item.
485    ///
486    /// Also accepts structs.
487    ///
488    /// # Panics
489    ///
490    /// Panics if the item is not a function-like item or a struct.
491    pub fn item_parameters(self, id: impl Into<hir::ItemId>) -> &'gcx [hir::VariableId] {
492        let id = id.into();
493        self.item_parameters_opt(id)
494            .unwrap_or_else(|| panic!("item_parameters: invalid item {id:?}"))
495    }
496
497    /// Returns the parameter variable declarations of the given function-like item.
498    ///
499    /// Also accepts structs.
500    pub fn item_parameters_opt(
501        self,
502        id: impl Into<hir::ItemId>,
503    ) -> Option<&'gcx [hir::VariableId]> {
504        self.hir.item(id).parameters()
505    }
506
507    /// Returns the return variable declarations of the given function-like item.
508    ///
509    /// # Panics
510    ///
511    /// Panics if the item is not a function-like item.
512    pub fn item_parameter_types(self, id: impl Into<hir::ItemId>) -> &'gcx [Ty<'gcx>] {
513        let id = id.into();
514        self.item_parameter_types_opt(id)
515            .unwrap_or_else(|| panic!("item_parameter_types: invalid item {id:?}"))
516    }
517
518    /// Returns the return variable declarations of the given function-like item.
519    ///
520    /// # Panics
521    ///
522    /// Panics if the item is not a function-like item.
523    pub fn item_parameter_types_opt(self, id: impl Into<hir::ItemId>) -> Option<&'gcx [Ty<'gcx>]> {
524        self.type_of_item(id.into()).parameters()
525    }
526
527    /// Returns the name of the given item.
528    #[inline]
529    pub fn item_name_opt(self, id: impl Into<hir::ItemId>) -> Option<Ident> {
530        self.hir.item(id).name()
531    }
532
533    /// Returns the span of the given item.
534    #[inline]
535    pub fn item_span(self, id: impl Into<hir::ItemId>) -> Span {
536        self.hir.item(id).span()
537    }
538
539    /// Returns the 4-byte selector of the given item. Only accepts functions and errors.
540    ///
541    /// # Panics
542    ///
543    /// Panics if the item is not a function or error.
544    pub fn function_selector(self, id: impl Into<hir::ItemId>) -> Selector {
545        let id = id.into();
546        assert!(
547            matches!(id, hir::ItemId::Function(_) | hir::ItemId::Error(_)),
548            "function_selector: invalid item {id:?}"
549        );
550        self.item_selector(id)[..4].try_into().unwrap()
551    }
552
553    /// Returns the 32-byte selector of the given event.
554    pub fn event_selector(self, id: hir::EventId) -> B256 {
555        self.item_selector(id.into())
556    }
557
558    /// Computes the [`Ty`] of the given [`hir::Type`]. Not cached.
559    pub fn type_of_hir_ty(self, ty: &hir::Type<'_>) -> Ty<'gcx> {
560        let kind = match ty.kind {
561            hir::TypeKind::Elementary(ty) => TyKind::Elementary(ty),
562            hir::TypeKind::Array(array) => {
563                let ty = self.type_of_hir_ty(&array.element);
564                match array.size {
565                    Some(size) => match crate::eval::ConstantEvaluator::new(self).eval(size) {
566                        Ok(int) => {
567                            if int.data.is_zero() {
568                                let msg = "array length must be greater than zero";
569                                let guar = self.dcx().err(msg).span(size.span).emit();
570                                TyKind::Array(self.mk_ty_err(guar), int.data)
571                            } else {
572                                TyKind::Array(ty, int.data)
573                            }
574                        }
575                        Err(guar) => TyKind::Array(self.mk_ty_err(guar), U256::from(1)),
576                    },
577                    None => TyKind::DynArray(ty),
578                }
579            }
580            hir::TypeKind::Function(f) => {
581                return self.mk_ty_fn_ptr(TyFnPtr {
582                    parameters: self.mk_item_tys(f.parameters),
583                    returns: self.mk_item_tys(f.returns),
584                    state_mutability: f.state_mutability,
585                    visibility: f.visibility,
586                });
587            }
588            hir::TypeKind::Mapping(mapping) => {
589                let key = self.type_of_hir_ty(&mapping.key);
590                let value = self.type_of_hir_ty(&mapping.value);
591                TyKind::Mapping(key, value)
592            }
593            hir::TypeKind::Custom(item) => return self.type_of_item_simple(item, ty.span),
594            hir::TypeKind::Err(guar) => TyKind::Err(guar),
595        };
596        self.mk_ty(kind)
597    }
598
599    fn type_of_item_simple(self, id: hir::ItemId, span: Span) -> Ty<'gcx> {
600        match id {
601            hir::ItemId::Contract(_)
602            | hir::ItemId::Struct(_)
603            | hir::ItemId::Enum(_)
604            | hir::ItemId::Udvt(_) => self.type_of_item(id),
605            _ => {
606                let msg = "name has to refer to a valid user-defined type";
607                self.mk_ty_err(self.dcx().err(msg).span(span).emit())
608            }
609        }
610    }
611
612    /// Returns the type of the given [`hir::Res`].
613    pub fn type_of_res(self, res: hir::Res) -> Ty<'gcx> {
614        match res {
615            hir::Res::Item(id) => self.type_of_item(id),
616            hir::Res::Namespace(id) => self.mk_ty(TyKind::Module(id)),
617            hir::Res::Builtin(builtin) => builtin.ty(self),
618            hir::Res::Err(guar) => self.mk_ty_err(guar),
619        }
620    }
621}
622
623macro_rules! cached {
624    ($($(#[$attr:meta])* $vis:vis fn $name:ident($gcx:ident: _, $key:ident : $key_type:ty) -> $value:ty $imp:block)*) => {
625        #[derive(Default)]
626        struct Cache<'gcx> {
627            $(
628                $name: FxOnceMap<$key_type, $value>,
629            )*
630        }
631
632        impl<'gcx> Gcx<'gcx> {
633            $(
634                $(#[$attr])*
635                $vis fn $name(self, $key: $key_type) -> $value {
636                    #[cfg(false)]
637                    let _guard = log_cache_query(stringify!($name), &$key);
638                    #[cfg(false)]
639                    let mut hit = true;
640                    let r = cache_insert(&self.cache.$name, $key, |&$key| {
641                        #[cfg(false)]
642                        {
643                            hit = false;
644                        }
645                        let $gcx = self;
646                        $imp
647                    });
648                    #[cfg(false)]
649                    log_cache_query_result(&r, hit);
650                    r
651                }
652            )*
653        }
654    };
655}
656
657cached! {
658/// Returns the [ERC-165] interface ID of the given contract.
659///
660/// This is the XOR of the selectors of all function selectors in the interface.
661///
662/// The solc implementation excludes inheritance: <https://github.com/argotorg/solidity/blob/ad2644c52b3afbe80801322c5fe44edb59383500/libsolidity/ast/AST.cpp#L310-L316>
663///
664/// See [ERC-165] for more details.
665///
666/// [ERC-165]: https://eips.ethereum.org/EIPS/eip-165
667pub fn interface_id(gcx: _, id: hir::ContractId) -> Selector {
668    let kind = gcx.hir.contract(id).kind;
669    assert!(kind.is_interface(), "{kind} {id:?} is not an interface");
670    let selectors = gcx.interface_functions(id).own().iter().map(|f| f.selector);
671    selectors.fold(Selector::ZERO, std::ops::BitXor::bitxor)
672}
673
674/// Returns all the exported functions of the given contract.
675///
676/// The contract doesn't have to be an interface.
677pub fn interface_functions(gcx: _, id: hir::ContractId) -> InterfaceFunctions<'gcx> {
678    let c = gcx.hir.contract(id);
679    let mut inheritance_start = None;
680    let mut signatures_seen = FxHashSet::default();
681    let mut hash_collisions = FxHashMap::default();
682    let functions = c.linearized_bases.iter().flat_map(|&base| {
683        let b = gcx.hir.contract(base);
684        let functions =
685            b.functions().filter(|&f| gcx.hir.function(f).is_part_of_external_interface());
686        if base == id {
687            assert!(inheritance_start.is_none(), "duplicate self ID in linearized_bases");
688            inheritance_start = Some(functions.clone().count());
689        }
690        functions
691    }).filter_map(|f_id| {
692        let f = gcx.hir.function(f_id);
693        let ty = gcx.type_of_item(f_id.into());
694        let TyKind::FnPtr(ty_f) = ty.kind else { unreachable!() };
695        let mut result = Ok(());
696        for (var_id, ty) in f.variables().zip(ty_f.tys()) {
697            if let Err(guar) = ty.has_error() {
698                result = Err(guar);
699                continue;
700            }
701            if !ty.can_be_exported() {
702                // TODO: implement `interfaceType`
703                if c.kind.is_library() {
704                    result = Err(ErrorGuaranteed::new_unchecked());
705                    continue;
706                }
707
708                let kind = f.description();
709                let msg = if ty.has_mapping() {
710                    format!("types containing mappings cannot be parameter or return types of public {kind}s")
711                } else if ty.is_recursive() {
712                    format!("recursive types cannot be parameter or return types of public {kind}s")
713                } else {
714                    format!("this type cannot be parameter or return type of a public {kind}")
715                };
716                let span = gcx.hir.variable(var_id).ty.span;
717                result = Err(gcx.dcx().err(msg).span(span).emit());
718            }
719        }
720        if result.is_err() {
721            return None;
722        }
723
724        // Virtual functions or ones with the same function parameter types are checked separately,
725        // skip them here to avoid reporting them as selector hash collision errors below.
726        let hash = gcx.item_selector(f_id.into());
727        let selector: Selector = hash[..4].try_into().unwrap();
728        if !signatures_seen.insert(hash) {
729            return None;
730        }
731
732        // Check for selector hash collisions.
733        if let Some(prev) = hash_collisions.insert(selector, f_id) {
734            let f2 = gcx.hir.function(prev);
735            let msg = "function signature hash collision";
736            let full_note = format!(
737                "the function signatures `{}` and `{}` produce the same 4-byte selector `{selector}`",
738                gcx.item_signature(f_id.into()),
739                gcx.item_signature(prev.into()),
740            );
741            gcx.dcx().err(msg).span(c.name.span).span_note(f.span, "first function").span_note(f2.span, "second function").note(full_note).emit();
742        }
743
744        Some(InterfaceFunction { selector, id: f_id, ty })
745    });
746    let functions = gcx.bump().alloc_from_iter(functions);
747    trace!("{}.interfaceFunctions.len() = {}", gcx.contract_fully_qualified_name(id), functions.len());
748    let inheritance_start = inheritance_start.expect("linearized_bases did not contain self ID");
749    InterfaceFunctions { functions, inheritance_start }
750}
751
752/// Returns the ABI signature of the given item. Only accepts functions, errors, and events.
753pub fn item_signature(gcx: _, id: hir::ItemId) -> &'gcx str {
754    let name = gcx.item_name(id);
755    let tys = gcx.item_parameter_types(id);
756    gcx.bump().alloc_str(&gcx.mk_abi_signature(name.as_str(), tys.iter().copied()))
757}
758
759pub(crate) fn item_selector(gcx: _, id: hir::ItemId) -> B256 {
760    keccak256(gcx.item_signature(id))
761}
762
763/// Returns the type of the given item.
764pub fn type_of_item(gcx: _, id: hir::ItemId) -> Ty<'gcx> {
765    let kind = match id {
766        hir::ItemId::Contract(id) => TyKind::Contract(id),
767        hir::ItemId::Function(id) => {
768            let f = gcx.hir.function(id);
769            TyKind::FnPtr(gcx.interner.intern_ty_fn_ptr(gcx.bump(), TyFnPtr {
770                parameters: gcx.mk_item_tys(f.parameters),
771                returns: gcx.mk_item_tys(f.returns),
772                state_mutability: f.state_mutability,
773                visibility: f.visibility,
774            }))
775        }
776        hir::ItemId::Variable(id) => {
777            let var = gcx.hir.variable(id);
778            let ty = gcx.type_of_hir_ty(&var.ty);
779            return var_type(gcx, var, ty);
780        }
781        hir::ItemId::Struct(id) => TyKind::Struct(id),
782        hir::ItemId::Enum(id) => TyKind::Enum(id),
783        hir::ItemId::Udvt(id) => {
784            let udvt = gcx.hir.udvt(id);
785            if udvt.ty.kind.is_elementary()
786                && let ty = gcx.type_of_hir_ty(&udvt.ty)
787                && ty.is_value_type()
788            {
789                TyKind::Udvt(ty, id)
790            } else {
791                let msg = "the underlying type of UDVTs must be an elementary value type";
792                TyKind::Err(gcx.dcx().err(msg).span(udvt.ty.span).emit())
793            }
794        }
795        hir::ItemId::Error(id) => {
796            TyKind::Error(gcx.mk_item_tys(gcx.hir.error(id).parameters), id)
797        }
798        hir::ItemId::Event(id) => {
799            TyKind::Event(gcx.mk_item_tys(gcx.hir.event(id).parameters), id)
800        }
801    };
802    gcx.mk_ty(kind)
803}
804
805/// Returns the types of the fields of the given struct.
806pub fn struct_field_types(gcx: _, id: hir::StructId) -> &'gcx [Ty<'gcx>] {
807    gcx.mk_ty_iter(gcx.hir.strukt(id).fields.iter().map(|&f| gcx.type_of_item(f.into())))
808}
809
810/// Returns the recursiveness of the given struct.
811pub fn struct_recursiveness(gcx: _, id: hir::StructId) -> Recursiveness {
812    use solar_data_structures::cycle::*;
813
814    let r = CycleDetector::detect(gcx, id, |gcx, cd, id| {
815        let s = gcx.hir.strukt(id);
816
817        if cd.depth() >= 256 {
818            let guar = gcx.dcx().err("struct is too deeply nested").span(s.span).emit();
819            return CycleDetectorResult::Break(Either::Left(guar));
820        }
821
822        for &field_id in s.fields {
823            let field = gcx.hir.variable(field_id);
824            let mut check = |ty: &hir::Type<'_>, dynamic: bool| {
825                if let hir::TypeKind::Custom(hir::ItemId::Struct(other)) = ty.kind {
826                    match cd.run(other) {
827                        CycleDetectorResult::Continue => {}
828                        CycleDetectorResult::Cycle(_) if dynamic => {
829                            return CycleDetectorResult::Break(Either::Right(()));
830                        }
831                        r => return r,
832                    }
833                }
834                CycleDetectorResult::Continue
835            };
836            let mut dynamic = false;
837            let mut ty = &field.ty;
838            while let hir::TypeKind::Array(array) = ty.kind {
839                if array.size.is_none() {
840                    dynamic = true;
841                }
842                ty = &array.element;
843            }
844            cdr_try!(check(ty, dynamic));
845            if let ControlFlow::Break(r) = field.ty.visit(&gcx.hir, &mut |ty| check(ty, true).to_controlflow()) {
846                return r;
847            }
848        }
849
850        CycleDetectorResult::Continue
851    });
852    match r {
853        CycleDetectorResult::Continue => Recursiveness::None,
854        CycleDetectorResult::Break(Either::Left(guar)) => Recursiveness::Infinite(guar),
855        CycleDetectorResult::Break(Either::Right(())) => Recursiveness::Recursive,
856        CycleDetectorResult::Cycle(id) => Recursiveness::Infinite(
857            gcx.dcx().err("recursive struct definition").span(gcx.item_span(id)).emit()
858        ),
859    }
860}
861
862/// Returns the members of the given type.
863pub fn members_of(gcx: _, ty: Ty<'gcx>) -> members::MemberList<'gcx> {
864    members::members_of(gcx, ty)
865}
866}
867
868fn var_type<'gcx>(gcx: Gcx<'gcx>, var: &'gcx hir::Variable<'gcx>, ty: Ty<'gcx>) -> Ty<'gcx> {
869    use hir::DataLocation::*;
870
871    // https://github.com/argotorg/solidity/blob/48d40d5eaf97c835cf55896a7a161eedc57c57f9/libsolidity/ast/AST.cpp#L820
872    let has_reference_or_mapping_type = ty.is_reference_type() || ty.has_mapping();
873    let mut func_vis = None;
874    let mut locs;
875    let allowed: &[_] = if var.is_state_variable() {
876        &[None, Some(Transient)]
877    } else if !has_reference_or_mapping_type || var.is_event_or_error_parameter() {
878        &[None]
879    } else if var.is_callable_or_catch_parameter() {
880        locs = SmallVec::<[_; 3]>::new();
881        locs.push(Some(Memory));
882        let mut is_constructor_parameter = false;
883        if let Some(f) = var.function {
884            let f = gcx.hir.function(f);
885            is_constructor_parameter = f.kind.is_constructor();
886            if !var.is_try_catch_parameter() && !is_constructor_parameter {
887                func_vis = Some(f.visibility);
888            }
889            if is_constructor_parameter
890                || f.visibility <= hir::Visibility::Internal
891                || f.contract.is_some_and(|c| gcx.hir.contract(c).kind.is_library())
892            {
893                locs.push(Some(Storage));
894            }
895        }
896        if !var.is_try_catch_parameter() && !is_constructor_parameter {
897            locs.push(Some(Calldata));
898        }
899        &locs
900    } else if var.is_local_variable() {
901        &[Some(Memory), Some(Storage), Some(Calldata)]
902    } else {
903        &[None]
904    };
905
906    let mut var_loc = var.data_location;
907    if !allowed.contains(&var_loc) {
908        if ty.has_error().is_ok() {
909            let msg = if !has_reference_or_mapping_type {
910                "data location can only be specified for array, struct or mapping types".to_string()
911            } else if let Some(var_loc) = var_loc {
912                format!("invalid data location `{var_loc}`")
913            } else {
914                "expected data location".to_string()
915            };
916            let mut err = gcx.dcx().err(msg).span(var.span);
917            if has_reference_or_mapping_type {
918                let note = format!(
919                    "data location must be {expected} for {vis}{descr}{got}",
920                    expected = or_list(
921                        allowed.iter().map(|d| format!("`{}`", DataLocation::opt_to_str(*d)))
922                    ),
923                    vis = if let Some(vis) = func_vis { format!("{vis} ") } else { String::new() },
924                    descr = var.description(),
925                    got = if let Some(var_loc) = var_loc {
926                        format!(", but got `{var_loc}`")
927                    } else {
928                        String::new()
929                    },
930                );
931                err = err.note(note);
932            }
933            err.emit();
934        }
935        var_loc = allowed[0];
936    }
937
938    let ty_loc = if var.is_event_or_error_parameter() || var.is_file_level_variable() {
939        Memory
940    } else if var.is_state_variable() {
941        let mut_specified = var.mutability.is_some();
942        match var_loc {
943            None => {
944                if mut_specified {
945                    Memory
946                } else {
947                    Storage
948                }
949            }
950            Some(Transient) => {
951                if mut_specified {
952                    let msg = "transient cannot be used as data location for constant or immutable variables";
953                    gcx.dcx().err(msg).span(var.span).emit();
954                }
955                if var.initializer.is_some() {
956                    let msg =
957                        "initialization of transient storage state variables is not supported";
958                    gcx.dcx().err(msg).span(var.span).emit();
959                }
960                Transient
961            }
962            Some(_) => unreachable!(),
963        }
964    } else if var.is_struct_member() {
965        Storage
966    } else {
967        match var_loc {
968            Some(loc @ (Memory | Storage | Calldata)) => loc,
969            Some(Transient) => unimplemented!(),
970            None => {
971                assert!(!has_reference_or_mapping_type, "data location not properly set");
972                Memory
973            }
974        }
975    };
976
977    if ty.is_reference_type() { ty.with_loc(gcx, ty_loc) } else { ty }
978}
979
980/// `OnceMap::insert` but with `Copy` keys and values.
981#[inline]
982fn cache_insert<K, V>(map: &FxOnceMap<K, V>, key: K, make_val: impl FnOnce(&K) -> V) -> V
983where
984    K: Copy + Eq + Hash,
985    V: Copy,
986{
987    map.map_insert(key, make_val, cache_insert_with_result)
988}
989
990#[inline]
991fn cache_insert_with_result<K, V: Copy>(_: &K, v: &V) -> V {
992    *v
993}
994
995#[cfg(false)]
996fn log_cache_query(name: &str, key: &dyn fmt::Debug) -> tracing::span::EnteredSpan {
997    let guard = trace_span!("query", %name, ?key).entered();
998    trace!("entered");
999    guard
1000}
1001
1002#[cfg(false)]
1003fn log_cache_query_result(result: &dyn fmt::Debug, hit: bool) {
1004    trace!(?result, hit);
1005}