Skip to main content

solar_sema/ty/
print.rs

1use super::{Gcx, Ty, TyFnKind, TyKind};
2use crate::hir;
3use alloy_json_abi as json;
4use solar_ast::{DataLocation, ElementaryType};
5use std::{fmt, ops::ControlFlow};
6
7impl<'gcx> Gcx<'gcx> {
8    /// Formats the ABI signature of a function in the form `{name}({tys},*)`.
9    pub(super) fn mk_abi_signature(
10        self,
11        name: &str,
12        tys: impl IntoIterator<Item = Ty<'gcx>>,
13        in_library: bool,
14    ) -> String {
15        let mut s = String::with_capacity(64);
16        s.push_str(name);
17        TyAbiPrinter::new(self, &mut s, TyAbiPrinterMode::Signature)
18            .with_in_library(in_library)
19            .print_tuple(tys)
20            .unwrap();
21        s
22    }
23
24    /// Returns the ABI of the given contract.
25    ///
26    /// Reference: <https://docs.soliditylang.org/en/develop/abi-spec.html>
27    pub fn contract_abi<'a>(self, id: hir::ContractId) -> Vec<json::AbiItem<'a>> {
28        let mut items = Vec::<json::AbiItem<'a>>::new();
29
30        let c = self.hir.contract(id);
31        if let Some(ctor) = c.ctor
32            && !c.is_abstract()
33        {
34            let json::Function { inputs, state_mutability, .. } = self.function_abi(ctor);
35            items.push(json::Constructor { inputs, state_mutability }.into());
36        }
37        if let Some(fallback) = c.fallback {
38            let json::Function { state_mutability, .. } = self.function_abi(fallback);
39            items.push(json::Fallback { state_mutability }.into());
40        }
41        if let Some(receive) = c.receive {
42            let json::Function { state_mutability, .. } = self.function_abi(receive);
43            items.push(json::Receive { state_mutability }.into());
44        }
45        for f in self.interface_functions(id) {
46            items.push(self.function_abi(f.id).into());
47        }
48        // TODO: Does not include referenced items: https://github.com/paradigmxyz/solar/issues/305
49        // See solc `interfaceEvents` and `interfaceErrors`.
50        for item in self.hir.contract_item_ids(id) {
51            match item {
52                hir::ItemId::Event(id) => items.push(self.event_abi(id).into()),
53                hir::ItemId::Error(id) => items.push(self.error_abi(id).into()),
54                _ => {}
55            }
56        }
57
58        // https://github.com/argotorg/solidity/blob/87d86bfba64d8b88537a4a85c1d71f521986b614/libsolidity/interface/ABI.cpp#L43-L47
59        fn cmp_key<'a>(item: &'a json::AbiItem<'_>) -> impl Ord + use<'a> {
60            (item.json_type(), item.name())
61        }
62        items.sort_by(|a, b| cmp_key(a).cmp(&cmp_key(b)));
63
64        items
65    }
66
67    fn function_abi(self, id: hir::FunctionId) -> json::Function {
68        let f = self.hir.function(id);
69        json::Function {
70            name: f.name.unwrap_or_default().to_string(),
71            inputs: f.parameters.iter().map(|&p| self.var_param_abi(p)).collect(),
72            outputs: f.returns.iter().map(|&p| self.var_param_abi(p)).collect(),
73            state_mutability: json_state_mutability(f.state_mutability),
74        }
75    }
76
77    fn event_abi(self, id: hir::EventId) -> json::Event {
78        let e = self.hir.event(id);
79        json::Event {
80            name: e.name.to_string(),
81            inputs: e.parameters.iter().map(|&p| self.event_param_abi(p)).collect(),
82            anonymous: e.anonymous,
83        }
84    }
85
86    fn error_abi(self, id: hir::ErrorId) -> json::Error {
87        let e = self.hir.error(id);
88        json::Error {
89            name: e.name.to_string(),
90            inputs: e.parameters.iter().map(|&p| self.var_param_abi(p)).collect(),
91        }
92    }
93
94    fn var_param_abi(self, id: hir::VariableId) -> json::Param {
95        let v = self.hir.variable(id);
96        let ty = self.type_of_item(id.into());
97        self.param_abi(ty, v.name.unwrap_or_default().to_string())
98    }
99
100    fn param_abi(self, ty: Ty<'gcx>, name: String) -> json::Param {
101        let ty = ty.peel_refs();
102        let struct_id = ty.visit(&mut |ty| match ty.kind {
103            TyKind::Struct(id) => ControlFlow::Break(id),
104            _ => ControlFlow::Continue(()),
105        });
106        json::Param {
107            ty: self.print_abi_param_ty(ty),
108            name,
109            components: match struct_id {
110                ControlFlow::Break(id) => self
111                    .item_fields(id)
112                    .map(|(ty, f)| self.param_abi(ty, self.item_name(f).to_string()))
113                    .collect(),
114                ControlFlow::Continue(()) => vec![],
115            },
116            internal_type: Some(json::InternalType::parse(&self.print_solc_param_ty(ty)).unwrap()),
117        }
118    }
119
120    fn event_param_abi(self, id: hir::VariableId) -> json::EventParam {
121        let json::Param { ty, name, components, internal_type } = self.var_param_abi(id);
122        let indexed = self.hir.variable(id).indexed;
123        json::EventParam { ty, name, components, internal_type, indexed }
124    }
125
126    fn print_abi_param_ty(self, ty: Ty<'gcx>) -> String {
127        let mut s = String::new();
128        TyAbiPrinter::new(self, &mut s, TyAbiPrinterMode::Abi).print(ty).unwrap();
129        s
130    }
131
132    fn print_solc_param_ty(self, ty: Ty<'gcx>) -> String {
133        let mut s = String::new();
134        TySolcPrinter::new(self, &mut s).data_locations(false).print(ty).unwrap();
135        s
136    }
137}
138
139fn json_state_mutability(s: hir::StateMutability) -> json::StateMutability {
140    match s {
141        hir::StateMutability::Pure => json::StateMutability::Pure,
142        hir::StateMutability::View => json::StateMutability::View,
143        hir::StateMutability::Payable => json::StateMutability::Payable,
144        hir::StateMutability::NonPayable => json::StateMutability::NonPayable,
145    }
146}
147
148/// Prints types as specified by the Solidity ABI.
149///
150/// Reference: <https://docs.soliditylang.org/en/latest/abi-spec.html>
151pub struct TyAbiPrinter<'gcx, W> {
152    gcx: Gcx<'gcx>,
153    buf: W,
154    mode: TyAbiPrinterMode,
155    /// Print types in the library function signature form used by solc.
156    ///
157    /// Unlike contract functions, library functions may take `mapping`/`storage`
158    /// reference parameters and refer to structs, enums, and contracts by name
159    /// (e.g. `f(DataTypes.Reserve storage)`).
160    in_library: bool,
161}
162
163/// [`TyAbiPrinter`] configuration.
164#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165pub enum TyAbiPrinterMode {
166    /// Printing types for a function signature.
167    ///
168    /// Prints the fields of the struct in a tuple, recursively.
169    ///
170    /// Note that this will make the printer panic if it encounters a recursive struct.
171    Signature,
172    /// Printing types for a JSON ABI `type` field.
173    ///
174    /// Print the word `tuple` when encountering structs.
175    Abi,
176}
177
178impl<'gcx, W: fmt::Write> TyAbiPrinter<'gcx, W> {
179    /// Creates a new ABI printer.
180    pub fn new(gcx: Gcx<'gcx>, buf: W, mode: TyAbiPrinterMode) -> Self {
181        Self { gcx, buf, mode, in_library: false }
182    }
183
184    /// Sets whether types are printed as a `library` function signature.
185    pub fn with_in_library(mut self, yes: bool) -> Self {
186        self.in_library = yes;
187        self
188    }
189
190    /// Returns a mutable reference to the underlying buffer.
191    pub fn buf(&mut self) -> &mut W {
192        &mut self.buf
193    }
194
195    /// Consumes the printer and returns the underlying buffer.
196    pub fn into_buf(self) -> W {
197        self.buf
198    }
199
200    /// Prints the ABI representation of `ty`.
201    pub fn print(&mut self, ty: Ty<'gcx>) -> fmt::Result {
202        match ty.kind {
203            TyKind::Elementary(ty) => ty.write_abi_str(&mut self.buf),
204            TyKind::Contract(id) if self.mode == TyAbiPrinterMode::Signature && self.in_library => {
205                write!(self.buf, "{}", self.gcx.item_canonical_name(id))
206            }
207            TyKind::Contract(_) => self.buf.write_str("address"),
208            TyKind::Fn(_) => self.buf.write_str("function"),
209            TyKind::Struct(id) => match self.mode {
210                TyAbiPrinterMode::Signature if self.in_library => {
211                    write!(self.buf, "{}", self.gcx.item_canonical_name(id))
212                }
213                TyAbiPrinterMode::Signature => {
214                    if self.gcx.struct_recursiveness(id).is_recursive() {
215                        assert!(
216                            self.gcx.dcx().has_errors().is_err(),
217                            "trying to print recursive struct and no error has been emitted"
218                        );
219                        write!(self.buf, "<recursive struct {}>", self.gcx.item_canonical_name(id))
220                    } else {
221                        self.print_tuple(self.gcx.struct_field_types(id).iter().copied())
222                    }
223                }
224                TyAbiPrinterMode::Abi => self.buf.write_str("tuple"),
225            },
226            TyKind::Enum(id) => match self.mode {
227                TyAbiPrinterMode::Signature if self.in_library => {
228                    write!(self.buf, "{}", self.gcx.item_canonical_name(id))
229                }
230                _ => self.buf.write_str("uint8"),
231            },
232            TyKind::Udvt(ty, _) => self.print(ty),
233            TyKind::Ref(ty, loc) => {
234                self.print(ty)?;
235                // solc encodes the `storage` location in `library` function
236                // signatures (e.g. `f(uint256[] storage)`), but never `memory`
237                // or `calldata`.
238                if self.in_library && loc == DataLocation::Storage {
239                    self.buf.write_str(" storage")?;
240                }
241                Ok(())
242            }
243            // A `mapping` can appear in a `library` function signature. solc prints
244            // it structurally as `mapping(key => value)`.
245            TyKind::Mapping(key, value) if self.mode == TyAbiPrinterMode::Signature => {
246                self.buf.write_str("mapping(")?;
247                self.print(key)?;
248                self.buf.write_str(" => ")?;
249                self.print(value)?;
250                self.buf.write_str(")")
251            }
252            TyKind::DynArray(ty) => {
253                self.print(ty)?;
254                self.buf.write_str("[]")
255            }
256            TyKind::Array(ty, len) => {
257                self.print(ty)?;
258                write!(self.buf, "[{len}]")
259            }
260
261            TyKind::Slice(..)
262            | TyKind::StringLiteral(..)
263            | TyKind::IntLiteral(..)
264            | TyKind::Tuple(_)
265            | TyKind::Mapping(..)
266            | TyKind::Super(_)
267            // ^ `Mapping` only reaches here in `Abi` mode; `Signature` mode handles it above.
268            | TyKind::Error(..)
269            | TyKind::Event(..)
270            | TyKind::Module(_)
271            | TyKind::BuiltinModule(_)
272            | TyKind::Variadic
273            | TyKind::Type(_)
274            | TyKind::Meta(_)
275            | TyKind::Err(_) => {
276                assert!(
277                    self.gcx.dcx().has_errors().is_err(),
278                    "printing unsupported type as ABI: {ty:?}"
279                );
280                self.buf.write_str("<error>")
281            }
282        }
283    }
284
285    /// Prints `tys` in a comma-delimited parenthesized tuple.
286    pub fn print_tuple(&mut self, tys: impl IntoIterator<Item = Ty<'gcx>>) -> fmt::Result {
287        self.buf.write_str("(")?;
288        for (i, ty) in tys.into_iter().enumerate() {
289            if i > 0 {
290                self.buf.write_str(",")?;
291            }
292            self.print(ty)?;
293        }
294        self.buf.write_str(")")
295    }
296}
297
298/// Prints types as implemented in `Type::toString(bool)` in solc.
299///
300/// This is mainly used in the `internalType` field of the ABI.
301///
302/// Example: <https://github.com/argotorg/solidity/blob/9d7cc42bc1c12bb43e9dccf8c6c36833fdfcbbca/libsolidity/ast/Types.cpp#L2352-L2358>
303pub(crate) struct TySolcPrinter<'gcx, W> {
304    gcx: Gcx<'gcx>,
305    buf: W,
306    data_locations: bool,
307}
308
309impl<'gcx, W: fmt::Write> TySolcPrinter<'gcx, W> {
310    pub(crate) fn new(gcx: Gcx<'gcx>, buf: W) -> Self {
311        Self { gcx, buf, data_locations: false }
312    }
313
314    /// Whether to print data locations for reference types.
315    ///
316    /// Default: `false`.
317    pub(crate) fn data_locations(mut self, yes: bool) -> Self {
318        self.data_locations = yes;
319        self
320    }
321
322    pub(crate) fn print(&mut self, ty: Ty<'gcx>) -> fmt::Result {
323        match ty.kind {
324            TyKind::Elementary(ty) => {
325                ty.write_abi_str(&mut self.buf)?;
326                if matches!(ty, ElementaryType::Address(true)) {
327                    self.buf.write_str(" payable")?;
328                }
329                Ok(())
330            }
331            TyKind::Contract(id) => {
332                let c = self.gcx.hir.contract(id);
333                self.buf.write_str(if c.kind.is_library() { "library" } else { "contract" })?;
334                write!(self.buf, " {}", c.name)
335            }
336            TyKind::Super(id) => {
337                let c = self.gcx.hir.contract(id);
338                write!(self.buf, "contract super {}", c.name)
339            }
340            TyKind::Fn(f) => {
341                self.buf.write_str("function ")?;
342                if f.is_declaration()
343                    && let Some(id) = f.function_id
344                {
345                    let name = self.gcx.item_canonical_name(hir::ItemId::from(id));
346                    write!(self.buf, "{name}")?;
347                }
348                self.print_tuple(f.parameters)?;
349
350                if f.state_mutability != hir::StateMutability::NonPayable {
351                    write!(self.buf, " {}", f.state_mutability)?;
352                }
353                if f.kind == TyFnKind::External {
354                    self.buf.write_str(" external")?;
355                }
356
357                if !f.returns.is_empty() {
358                    self.buf.write_str(" returns ")?;
359                    self.print_tuple(f.returns)?;
360                }
361                Ok(())
362            }
363            TyKind::Struct(id) => {
364                write!(self.buf, "struct {}", self.gcx.item_canonical_name(id))
365            }
366            TyKind::Enum(id) => write!(self.buf, "enum {}", self.gcx.item_canonical_name(id)),
367            TyKind::Udvt(_, id) => write!(self.buf, "{}", self.gcx.item_canonical_name(id)),
368            TyKind::Ref(ty, loc) => {
369                self.print(ty)?;
370                if self.data_locations {
371                    write!(self.buf, " {loc}")?;
372                }
373                Ok(())
374            }
375            TyKind::DynArray(ty) => {
376                self.print(ty)?;
377                self.buf.write_str("[]")
378            }
379            TyKind::Array(ty, len) => {
380                self.print(ty)?;
381                write!(self.buf, "[{len}]")
382            }
383
384            // Internal types.
385            TyKind::StringLiteral(utf8, size) => {
386                let kind = if utf8 { "utf8" } else { "bytes" };
387                write!(self.buf, "{kind}_string_literal[{}]", size.bytes())
388            }
389            TyKind::IntLiteral(_, size, _) => {
390                write!(self.buf, "int_literal[{}]", size.bits())
391            }
392            TyKind::Slice(ty) => {
393                self.print(ty)?;
394                self.buf.write_str(" slice")
395            }
396            TyKind::Tuple(tys) => {
397                self.buf.write_str("tuple")?;
398                self.print_tuple(tys)
399            }
400            TyKind::Mapping(key, value) => {
401                self.buf.write_str("mapping(")?;
402                self.print(key)?;
403                self.buf.write_str(" => ")?;
404                self.print(value)?;
405                self.buf.write_str(")")
406            }
407            TyKind::Module(id) => {
408                let s = self.gcx.hir.source(id);
409                write!(self.buf, "module {}", s.file.name.display())
410            }
411            TyKind::BuiltinModule(b) => self.buf.write_str(b.name().as_str()),
412            TyKind::Variadic => self.buf.write_str("..."),
413            TyKind::Type(ty) | TyKind::Meta(ty) => {
414                self.buf.write_str("type(")?;
415                self.print(ty)?; // TODO: `richIdentifier`
416                self.buf.write_str(")")
417            }
418            TyKind::Error(tys, id) => {
419                self.buf.write_str("error ")?;
420                write!(self.buf, "{}", self.gcx.item_canonical_name(id))?;
421                self.print_tuple(tys)
422            }
423            TyKind::Event(tys, id) => {
424                self.buf.write_str("event ")?;
425                write!(self.buf, "{}", self.gcx.item_canonical_name(id))?;
426                self.print_tuple(tys)
427            }
428
429            TyKind::Err(_) => self.buf.write_str("<error>"),
430        }
431    }
432
433    fn print_tuple(&mut self, tys: &[Ty<'gcx>]) -> fmt::Result {
434        self.buf.write_str("(")?;
435        for (i, &ty) in tys.iter().enumerate() {
436            if i > 0 {
437                self.buf.write_str(",")?;
438            }
439            self.print(ty)?;
440        }
441        self.buf.write_str(")")
442    }
443}