solar_sema/ty/
ty.rs

1use super::{Gcx, Recursiveness, abi::TySolcPrinter};
2use crate::{builtins::Builtin, hir};
3use alloy_primitives::U256;
4use solar_ast::{DataLocation, ElementaryType, StateMutability, TypeSize, Visibility};
5use solar_data_structures::{Interned, fmt};
6use solar_interface::diagnostics::ErrorGuaranteed;
7use std::{borrow::Borrow, hash::Hash, ops::ControlFlow};
8
9/// An interned type.
10#[derive(Clone, Copy, PartialEq, Eq, Hash)]
11pub struct Ty<'gcx>(pub(super) Interned<'gcx, TyData<'gcx>>);
12
13impl fmt::Debug for Ty<'_> {
14    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15        self.0.fmt(f)
16    }
17}
18
19impl<'gcx> std::ops::Deref for Ty<'gcx> {
20    type Target = &'gcx TyData<'gcx>;
21
22    #[inline(always)]
23    fn deref(&self) -> &Self::Target {
24        &self.0.0
25    }
26}
27
28impl<'gcx> Ty<'gcx> {
29    pub fn new(gcx: Gcx<'gcx>, kind: TyKind<'gcx>) -> Self {
30        gcx.mk_ty(kind)
31    }
32
33    // TODO: with_loc_if_ref ?
34    pub fn with_loc(self, gcx: Gcx<'gcx>, loc: DataLocation) -> Self {
35        let mut ty = self;
36        if let TyKind::Ref(inner, l2) = self.kind {
37            if l2 == loc {
38                return self;
39            }
40            ty = inner;
41        }
42        Self::new(gcx, TyKind::Ref(ty, loc))
43    }
44
45    /// Peels `Ref` layers from the type, returning the inner type.
46    pub fn peel_refs(self) -> Self {
47        let mut ty = self;
48        while let TyKind::Ref(inner, _) = ty.kind {
49            ty = inner;
50        }
51        ty
52    }
53
54    pub fn as_externally_callable_function(self, gcx: Gcx<'gcx>) -> Self {
55        let is_calldata = |param: &Ty<'_>| param.is_ref_at(DataLocation::Calldata);
56        let parameters = self.parameters().unwrap_or_default();
57        let returns = self.returns().unwrap_or_default();
58        let any_parameter = parameters.iter().any(is_calldata);
59        let any_return = returns.iter().any(is_calldata);
60        if !any_parameter && !any_return {
61            return self;
62        }
63        gcx.mk_ty_fn_ptr(TyFnPtr {
64            parameters: if any_parameter {
65                gcx.mk_ty_iter(parameters.iter().map(|param| {
66                    if is_calldata(param) {
67                        param.with_loc(gcx, DataLocation::Memory)
68                    } else {
69                        *param
70                    }
71                }))
72            } else {
73                parameters
74            },
75            returns: if any_return {
76                gcx.mk_ty_iter(returns.iter().map(|ret| {
77                    if is_calldata(ret) { ret.with_loc(gcx, DataLocation::Memory) } else { *ret }
78                }))
79            } else {
80                returns
81            },
82            state_mutability: self.state_mutability().unwrap_or(StateMutability::NonPayable),
83            visibility: self.visibility().unwrap_or(Visibility::Public),
84        })
85    }
86
87    pub fn make_ref(self, gcx: Gcx<'gcx>, loc: DataLocation) -> Self {
88        if self.is_ref_at(loc) {
89            return self;
90        }
91        Self::new(gcx, TyKind::Ref(self, loc))
92    }
93
94    pub fn make_type_type(self, gcx: Gcx<'gcx>) -> Self {
95        if let TyKind::Type(_) = self.kind {
96            return self;
97        }
98        Self::new(gcx, TyKind::Type(self))
99    }
100
101    pub fn make_meta(self, gcx: Gcx<'gcx>) -> Self {
102        if let TyKind::Meta(_) = self.kind {
103            return self;
104        }
105        Self::new(gcx, TyKind::Meta(self))
106    }
107
108    /// Returns `true` if the type is a reference.
109    #[inline]
110    pub fn is_ref(self) -> bool {
111        matches!(self.kind, TyKind::Ref(..))
112    }
113
114    /// Returns `true` if the type is a reference to the given location.
115    #[inline]
116    pub fn is_ref_at(self, loc: DataLocation) -> bool {
117        matches!(self.kind, TyKind::Ref(_, l) if l == loc)
118    }
119
120    /// Returns `true` if the type is a value type.
121    ///
122    /// Reference: <https://docs.soliditylang.org/en/latest/types.html#value-types>
123    #[inline]
124    pub fn is_value_type(self) -> bool {
125        match self.kind {
126            TyKind::Elementary(t) => t.is_value_type(),
127            TyKind::Contract(_) | TyKind::FnPtr(_) | TyKind::Enum(_) | TyKind::Udvt(..) => true,
128            _ => false,
129        }
130    }
131
132    /// Returns `true` if the type is a reference type.
133    #[inline]
134    pub fn is_reference_type(self) -> bool {
135        match self.kind {
136            TyKind::Elementary(t) => t.is_reference_type(),
137            TyKind::Struct(_) | TyKind::Array(..) | TyKind::DynArray(_) => true,
138            _ => false,
139        }
140    }
141
142    /// Returns `true` if the type is recursive.
143    pub fn is_recursive(self) -> bool {
144        self.flags.contains(TyFlags::IS_RECURSIVE)
145    }
146
147    /// Returns `true` if this type contains a mapping.
148    pub fn has_mapping(self) -> bool {
149        self.flags.contains(TyFlags::HAS_MAPPING)
150    }
151
152    /// Returns `true` if this type contains an error.
153    pub fn has_error(self) -> Result<(), ErrorGuaranteed> {
154        if self.flags.contains(TyFlags::HAS_ERROR) {
155            Err(ErrorGuaranteed::new_unchecked())
156        } else {
157            Ok(())
158        }
159    }
160
161    /// Returns `true` if this type can be part of an externally callable function.
162    #[inline]
163    pub fn can_be_exported(self) -> bool {
164        !(self.is_recursive() || self.has_mapping() || self.has_error().is_err())
165    }
166
167    /// Returns the parameter types of the type.
168    #[inline]
169    pub fn parameters(self) -> Option<&'gcx [Self]> {
170        Some(match self.kind {
171            TyKind::FnPtr(f) => f.parameters,
172            TyKind::Event(tys, _) | TyKind::Error(tys, _) => tys,
173            _ => return None,
174        })
175    }
176
177    /// Returns the return types of the type.
178    #[inline]
179    pub fn returns(self) -> Option<&'gcx [Self]> {
180        Some(match self.kind {
181            TyKind::FnPtr(f) => f.returns,
182            _ => return None,
183        })
184    }
185
186    /// Returns the state mutability of the type.
187    #[inline]
188    pub fn state_mutability(self) -> Option<StateMutability> {
189        match self.kind {
190            TyKind::FnPtr(f) => Some(f.state_mutability),
191            _ => None,
192        }
193    }
194
195    /// Returns the visibility of the type.
196    #[inline]
197    pub fn visibility(self) -> Option<Visibility> {
198        match self.kind {
199            TyKind::FnPtr(f) => Some(f.visibility),
200            _ => None,
201        }
202    }
203
204    /// Visits the type and its subtypes.
205    pub fn visit<T>(self, f: &mut impl FnMut(Self) -> ControlFlow<T>) -> ControlFlow<T> {
206        f(self)?;
207        match self.kind {
208            TyKind::Elementary(_)
209            | TyKind::StringLiteral(..)
210            | TyKind::IntLiteral(_)
211            | TyKind::Contract(_)
212            | TyKind::FnPtr(_)
213            | TyKind::Enum(_)
214            | TyKind::Module(_)
215            | TyKind::BuiltinModule(_)
216            | TyKind::Struct(_)
217            | TyKind::Err(_) => ControlFlow::Continue(()),
218
219            TyKind::Ref(ty, _)
220            | TyKind::DynArray(ty)
221            | TyKind::Array(ty, _)
222            | TyKind::Udvt(ty, _)
223            | TyKind::Type(ty)
224            | TyKind::Meta(ty) => ty.visit(f),
225
226            TyKind::Error(list, _) | TyKind::Event(list, _) | TyKind::Tuple(list) => {
227                for ty in list {
228                    ty.visit(f)?;
229                }
230                ControlFlow::Continue(())
231            }
232
233            TyKind::Mapping(k, v) => {
234                k.visit(f)?;
235                v.visit(f)
236            }
237        }
238    }
239
240    /// Displays the type with the default format.
241    pub fn display(self, gcx: Gcx<'gcx>) -> impl fmt::Display + use<'gcx> {
242        fmt::from_fn(move |f| TySolcPrinter::new(gcx, f).data_locations(true).print(self))
243    }
244}
245
246/// The interned data of a type.
247pub struct TyData<'gcx> {
248    pub kind: TyKind<'gcx>,
249    pub flags: TyFlags,
250}
251
252impl<'gcx> Borrow<TyKind<'gcx>> for &TyData<'gcx> {
253    #[inline]
254    fn borrow(&self) -> &TyKind<'gcx> {
255        &self.kind
256    }
257}
258
259impl PartialEq for TyData<'_> {
260    #[inline]
261    fn eq(&self, other: &Self) -> bool {
262        self.kind == other.kind
263    }
264}
265
266impl Eq for TyData<'_> {}
267
268impl std::hash::Hash for TyData<'_> {
269    #[inline]
270    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
271        self.kind.hash(state);
272    }
273}
274
275impl fmt::Debug for TyData<'_> {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        self.kind.fmt(f)
278    }
279}
280
281/// The kind of a type.
282#[derive(Debug, PartialEq, Eq, Hash)]
283#[non_exhaustive]
284pub enum TyKind<'gcx> {
285    /// An elementary/primitive type.
286    Elementary(ElementaryType),
287
288    /// Any string literal. Contains `(is_valid_utf8(s), min(s.len(), 32))`.
289    /// - all string literals can coerce to `bytes`
290    /// - only valid UTF-8 string literals can coerce to `string`
291    /// - only string literals with `len <= N` can coerce to `bytesN`
292    StringLiteral(bool, TypeSize),
293
294    /// Any integer or fixed-point number literal. Contains `min(s.len(), 32)`.
295    IntLiteral(TypeSize),
296
297    /// A reference to another type which lives in the data location.
298    Ref(Ty<'gcx>, DataLocation),
299
300    /// Dynamic array: `T[]`.
301    DynArray(Ty<'gcx>),
302
303    /// Fixed-size array: `T[N]`.
304    Array(Ty<'gcx>, U256),
305
306    /// Tuple: `(T1, T2, ...)`.
307    Tuple(&'gcx [Ty<'gcx>]),
308
309    /// Mapping: `mapping(K => V)`.
310    Mapping(Ty<'gcx>, Ty<'gcx>),
311
312    /// Function pointer: `function(...) returns (...)`.
313    FnPtr(&'gcx TyFnPtr<'gcx>),
314
315    /// Contract.
316    Contract(hir::ContractId),
317
318    /// A struct.
319    ///
320    /// Cannot contain the types of its fields because it can be recursive.
321    Struct(hir::StructId),
322
323    /// An enum.
324    Enum(hir::EnumId),
325
326    /// A custom error.
327    Error(&'gcx [Ty<'gcx>], hir::ErrorId),
328
329    /// An event.
330    Event(&'gcx [Ty<'gcx>], hir::EventId),
331
332    /// A user-defined value type. `Ty` can only be `Elementary`.
333    Udvt(Ty<'gcx>, hir::UdvtId),
334
335    /// A source imported as a module: `import "path" as Module;`.
336    Module(hir::SourceId),
337
338    /// Builtin module.
339    BuiltinModule(Builtin),
340
341    /// The self-referential type, e.g. `Enum` in `Enum.Variant`.
342    /// Corresponds to `TypeType` in solc.
343    Type(Ty<'gcx>),
344
345    /// The meta type: `type(<inner_type>)`.
346    Meta(Ty<'gcx>),
347
348    /// An invalid type. Silences further errors.
349    Err(ErrorGuaranteed),
350}
351
352#[derive(Debug, PartialEq, Eq, Hash)]
353pub struct TyFnPtr<'gcx> {
354    pub parameters: &'gcx [Ty<'gcx>],
355    pub returns: &'gcx [Ty<'gcx>],
356    pub state_mutability: StateMutability,
357    pub visibility: Visibility,
358}
359
360impl<'gcx> TyFnPtr<'gcx> {
361    /// Returns an iterator over all the types in the function pointer.
362    pub fn tys(&self) -> impl DoubleEndedIterator<Item = Ty<'gcx>> + Clone {
363        self.parameters.iter().copied().chain(self.returns.iter().copied())
364    }
365}
366
367bitflags::bitflags! {
368    /// [`Ty`] flags.
369    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
370    pub struct TyFlags: u8 {
371        /// Whether this type is recursive.
372        const IS_RECURSIVE = 1 << 0;
373        /// Whether this type contains a mapping.
374        const HAS_MAPPING  = 1 << 1;
375        /// Whether an error is reachable.
376        const HAS_ERROR    = 1 << 2;
377    }
378}
379
380impl TyFlags {
381    pub(super) fn calculate<'gcx>(gcx: Gcx<'gcx>, ty: &TyKind<'gcx>) -> Self {
382        let mut flags = Self::empty();
383        flags.add_ty_kind(gcx, ty);
384        flags
385    }
386
387    fn add_ty_kind<'gcx>(&mut self, gcx: Gcx<'gcx>, ty: &TyKind<'gcx>) {
388        match *ty {
389            TyKind::Elementary(_)
390            | TyKind::StringLiteral(..)
391            | TyKind::IntLiteral(_)
392            | TyKind::Contract(_)
393            | TyKind::FnPtr(_)
394            | TyKind::Enum(_)
395            | TyKind::Module(_)
396            | TyKind::BuiltinModule(_) => {}
397
398            TyKind::Ref(ty, _)
399            | TyKind::DynArray(ty)
400            | TyKind::Array(ty, _)
401            | TyKind::Udvt(ty, _)
402            | TyKind::Type(ty)
403            | TyKind::Meta(ty) => self.add_ty(ty),
404
405            TyKind::Error(list, _) | TyKind::Event(list, _) | TyKind::Tuple(list) => {
406                self.add_tys(list)
407            }
408
409            TyKind::Mapping(k, v) => {
410                self.add_ty(k);
411                self.add_ty(v);
412                self.add(Self::HAS_MAPPING);
413            }
414
415            TyKind::Struct(id) => match gcx.struct_recursiveness(id) {
416                Recursiveness::None => self.add_tys(gcx.struct_field_types(id)),
417                Recursiveness::Recursive => {
418                    self.add(Self::IS_RECURSIVE);
419                }
420                Recursiveness::Infinite(_guar) => {
421                    self.add(Self::HAS_ERROR);
422                }
423            },
424
425            TyKind::Err(_) => self.add(Self::HAS_ERROR),
426        }
427    }
428
429    #[inline]
430    fn add(&mut self, other: Self) {
431        *self |= other;
432    }
433
434    #[inline]
435    fn add_ty(&mut self, ty: Ty<'_>) {
436        self.add(ty.flags);
437    }
438
439    #[inline]
440    fn add_tys(&mut self, tys: &[Ty<'_>]) {
441        for &ty in tys {
442            self.add_ty(ty);
443        }
444    }
445}