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#[derive(Clone, Copy, Debug)]
51pub struct InterfaceFunction<'gcx> {
52 pub id: hir::FunctionId,
54 pub selector: Selector,
56 pub ty: Ty<'gcx>,
58}
59
60#[derive(Clone, Copy, Debug)]
64pub struct InterfaceFunctions<'gcx> {
65 pub functions: &'gcx [InterfaceFunction<'gcx>],
67 pub inheritance_start: usize,
69}
70
71impl<'gcx> InterfaceFunctions<'gcx> {
72 pub fn all(&self) -> &'gcx [InterfaceFunction<'gcx>] {
74 self.functions
75 }
76
77 pub fn own(&self) -> &'gcx [InterfaceFunction<'gcx>] {
79 &self.functions[..self.inheritance_start]
80 }
81
82 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#[derive(Clone, Copy, Debug)]
109pub enum Recursiveness {
110 None,
112 Recursive,
114 Infinite(ErrorGuaranteed),
116}
117
118impl Recursiveness {
119 #[inline]
121 pub fn is_none(self) -> bool {
122 matches!(self, Self::None)
123 }
124
125 #[inline]
127 pub fn is_recursive(self) -> bool {
128 !self.is_none()
129 }
130}
131
132#[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#[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
216pub 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 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 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 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 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 pub fn get_file(self, name: impl Into<FileName>) -> Option<Arc<SourceFile>> {
403 self.sess.source_map().get_file(name)
404 }
405
406 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 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 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 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 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 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 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 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 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 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 #[inline]
529 pub fn item_name_opt(self, id: impl Into<hir::ItemId>) -> Option<Ident> {
530 self.hir.item(id).name()
531 }
532
533 #[inline]
535 pub fn item_span(self, id: impl Into<hir::ItemId>) -> Span {
536 self.hir.item(id).span()
537 }
538
539 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 pub fn event_selector(self, id: hir::EventId) -> B256 {
555 self.item_selector(id.into())
556 }
557
558 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 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! {
658pub 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
674pub 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 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 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 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
752pub 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
763pub 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
805pub 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
810pub 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
862pub 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 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#[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}