Skip to main content

solar_sema/builtins/
members.rs

1use super::Builtin;
2use crate::{
3    hir,
4    ty::{Gcx, Ty, TyFn, TyFnKind, TyKind},
5};
6use solar_ast::{DataLocation, ElementaryType, StateMutability as SM, Visibility};
7use solar_data_structures::BumpExt;
8use solar_interface::Symbol;
9
10pub type MemberList<'gcx> = &'gcx [Member<'gcx>];
11pub(crate) type MemberListOwned<'gcx> = Vec<Member<'gcx>>;
12
13pub(crate) fn native_members<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> MemberList<'gcx> {
14    let expected_ref = || panic!("native_members: type {ty:?} should be wrapped in Ref");
15    gcx.bump().alloc_vec(match ty.kind {
16        TyKind::Elementary(elementary_type) => match elementary_type {
17            ElementaryType::Address(false) => address(gcx).collect(),
18            ElementaryType::Address(true) => address_payable(gcx).collect(),
19            ElementaryType::Bool => Default::default(),
20            ElementaryType::String => Default::default(),
21            ElementaryType::Bytes => expected_ref(),
22            ElementaryType::Fixed(..) | ElementaryType::UFixed(..) => Default::default(),
23            ElementaryType::Int(_size) => Default::default(),
24            ElementaryType::UInt(_size) => Default::default(),
25            ElementaryType::FixedBytes(_size) => fixed_bytes(gcx),
26        },
27        TyKind::StringLiteral(_utf8, _size) => Default::default(),
28        TyKind::IntLiteral(..) => Default::default(),
29        TyKind::Ref(inner, loc) => reference(gcx, ty, inner, loc),
30        TyKind::DynArray(_ty) => expected_ref(),
31        TyKind::Array(_ty, _len) => expected_ref(),
32        TyKind::Slice(_ty) => Default::default(),
33        TyKind::Tuple(_tys) => Default::default(),
34        TyKind::Mapping(..) => Default::default(),
35        TyKind::Fn(f) => function(gcx, f),
36        TyKind::Contract(id) => contract(gcx, id),
37        TyKind::Super(_id) => Default::default(),
38        TyKind::Struct(_id) => expected_ref(),
39        TyKind::Enum(_id) => Default::default(),
40        TyKind::Udvt(_ty, _id) => Default::default(),
41        TyKind::Error(_tys, _id) => Member::of_builtins(gcx, [Builtin::FunctionSelector]),
42        TyKind::Event(_tys, id) => {
43            if gcx.hir.event(id).anonymous {
44                Default::default()
45            } else {
46                Member::of_builtins(gcx, [Builtin::EventSelector])
47            }
48        }
49        TyKind::Module(id) => gcx.symbol_resolver.source_scopes[id]
50            .iter()
51            .flat_map(|(name, decls)| {
52                decls.iter().map(move |decl| Member::new(name, gcx.type_of_res(decl.res)))
53            })
54            .collect(),
55        TyKind::BuiltinModule(builtin) => builtin
56            .members()
57            .unwrap_or_else(|| panic!("builtin module {builtin:?} has no inner builtins"))
58            .map(|b| Member::of_builtin(gcx, b))
59            .collect(),
60        TyKind::Variadic => Default::default(),
61        TyKind::Type(ty) => type_type(gcx, ty),
62        TyKind::Meta(ty) => meta(gcx, ty),
63        TyKind::Err(_guar) => Default::default(),
64    })
65}
66
67pub(crate) fn contract_type_members_in_context<'gcx>(
68    gcx: Gcx<'gcx>,
69    id: hir::ContractId,
70    current_contract: hir::ContractId,
71) -> MemberList<'gcx> {
72    gcx.bump().alloc_vec(contract_type(gcx, id, Some(current_contract)))
73}
74
75#[derive(Clone, Copy, Debug)]
76pub struct Member<'gcx> {
77    pub name: Symbol,
78    pub ty: Ty<'gcx>,
79    pub res: Option<hir::Res>,
80    /// Whether this is a `using for` function whose first parameter is bound to the receiver.
81    pub attached: bool,
82}
83
84impl<'gcx> Member<'gcx> {
85    pub fn new(name: Symbol, ty: Ty<'gcx>) -> Self {
86        Self { name, ty, res: None, attached: false }
87    }
88
89    pub fn with_res(name: Symbol, ty: Ty<'gcx>, res: impl Into<hir::Res>) -> Self {
90        Self { name, ty, res: Some(res.into()), attached: false }
91    }
92
93    pub fn with_attached_function(name: Symbol, ty: Ty<'gcx>, function: hir::FunctionId) -> Self {
94        Self { name, ty, res: Some(hir::ItemId::from(function).into()), attached: true }
95    }
96
97    pub fn with_builtin(builtin: Builtin, ty: Ty<'gcx>) -> Self {
98        Self::with_res(builtin.name(), ty, builtin)
99    }
100
101    pub fn with_attached_builtin(builtin: Builtin, ty: Ty<'gcx>) -> Self {
102        Self { name: builtin.name(), ty, res: Some(builtin.into()), attached: true }
103    }
104
105    pub fn of_builtin(gcx: Gcx<'gcx>, builtin: Builtin) -> Self {
106        Self::with_builtin(builtin, builtin.ty(gcx))
107    }
108
109    pub fn of_builtins(
110        gcx: Gcx<'gcx>,
111        builtins: impl IntoIterator<Item = Builtin>,
112    ) -> MemberListOwned<'gcx> {
113        Self::of_builtins_iter(gcx, builtins).collect()
114    }
115
116    pub fn of_builtins_iter(
117        gcx: Gcx<'gcx>,
118        builtins: impl IntoIterator<Item = Builtin>,
119    ) -> impl Iterator<Item = Self> {
120        builtins.into_iter().map(move |builtin| Self::of_builtin(gcx, builtin))
121    }
122}
123
124fn address(gcx: Gcx<'_>) -> impl Iterator<Item = Member<'_>> {
125    Member::of_builtins_iter(
126        gcx,
127        [
128            Builtin::AddressBalance,
129            Builtin::AddressCode,
130            Builtin::AddressCodehash,
131            Builtin::AddressCall,
132            Builtin::AddressDelegatecall,
133            Builtin::AddressStaticcall,
134        ],
135    )
136}
137
138fn address_payable(gcx: Gcx<'_>) -> impl Iterator<Item = Member<'_>> {
139    address(gcx).chain(Member::of_builtins_iter(
140        gcx,
141        [Builtin::AddressPayableTransfer, Builtin::AddressPayableSend],
142    ))
143}
144
145fn fixed_bytes(gcx: Gcx<'_>) -> MemberListOwned<'_> {
146    Member::of_builtins(gcx, [Builtin::FixedBytesLength])
147}
148
149pub(crate) fn contract(gcx: Gcx<'_>, id: hir::ContractId) -> MemberListOwned<'_> {
150    let c = gcx.hir.contract(id);
151    if c.kind.is_library() {
152        return MemberListOwned::default();
153    }
154    gcx.interface_functions(id)
155        .iter()
156        .map(|f| {
157            let id = hir::ItemId::from(f.id);
158            Member::with_res(
159                gcx.item_name(id).name,
160                f.ty.as_externally_callable_function(false, gcx),
161                id,
162            )
163        })
164        .collect()
165}
166
167fn function<'gcx>(gcx: Gcx<'gcx>, f: &'gcx TyFn<'gcx>) -> MemberListOwned<'gcx> {
168    let mut members = Vec::with_capacity(2);
169    if f.has_selector() {
170        members.push(Member::of_builtin(gcx, Builtin::FunctionSelector));
171    }
172    if f.has_address() {
173        members.push(Member::of_builtin(gcx, Builtin::FunctionAddress));
174    }
175    members
176}
177
178fn reference<'gcx>(
179    gcx: Gcx<'gcx>,
180    this: Ty<'gcx>,
181    inner: Ty<'gcx>,
182    loc: DataLocation,
183) -> MemberListOwned<'gcx> {
184    match (&inner.kind, loc) {
185        (&TyKind::Struct(id), _) => {
186            let fields = gcx.hir.strukt(id).fields;
187            let tys = gcx.struct_field_types(id);
188            debug_assert_eq!(fields.len(), tys.len());
189            fields
190                .iter()
191                .zip(tys)
192                .map(|(&f, &ty)| Member::new(gcx.item_name(f).name, ty.with_loc_if_ref(gcx, loc)))
193                .collect()
194        }
195        (
196            TyKind::DynArray(_) | TyKind::Elementary(ElementaryType::Bytes),
197            DataLocation::Storage,
198        ) => {
199            let inner = if let TyKind::DynArray(inner) = inner.kind {
200                inner.with_loc_if_ref(gcx, loc)
201            } else {
202                gcx.types.fixed_bytes(1)
203            };
204            vec![
205                Member::of_builtin(gcx, Builtin::ArrayLength),
206                Member::with_attached_builtin(
207                    Builtin::ArrayPush0,
208                    gcx.mk_builtin_fn(&[this], SM::NonPayable, &[inner]),
209                ),
210                Member::with_attached_builtin(
211                    Builtin::ArrayPush,
212                    gcx.mk_builtin_fn(&[this, inner], SM::NonPayable, &[]),
213                ),
214                Member::with_attached_builtin(
215                    Builtin::ArrayPop,
216                    gcx.mk_builtin_fn(&[this], SM::NonPayable, &[]),
217                ),
218            ]
219        }
220        (
221            TyKind::Array(..) | TyKind::DynArray(_) | TyKind::Elementary(ElementaryType::Bytes),
222            _,
223        ) => array(gcx),
224        _ => Default::default(),
225    }
226}
227
228// `Enum.Variant`, `Udvt.wrap`
229fn type_type<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> MemberListOwned<'gcx> {
230    match ty.kind {
231        TyKind::Contract(id) => contract_type(gcx, id, None),
232        TyKind::Super(id) => super_type(gcx, id),
233        TyKind::Enum(id) => {
234            gcx.hir.enumm(id).variants.iter().map(|v| Member::new(v.name, ty)).collect()
235        }
236        TyKind::Udvt(inner, _id) => {
237            vec![
238                Member::with_builtin(
239                    Builtin::UdvtWrap,
240                    gcx.mk_builtin_fn(&[inner], SM::Pure, &[ty]),
241                ),
242                Member::with_builtin(
243                    Builtin::UdvtUnwrap,
244                    gcx.mk_builtin_fn(&[ty], SM::Pure, &[inner]),
245                ),
246            ]
247        }
248        TyKind::Elementary(ElementaryType::String) => string_ty(gcx),
249        TyKind::Elementary(ElementaryType::Bytes) => bytes_ty(gcx),
250        _ => Default::default(),
251    }
252}
253
254fn contract_type(
255    gcx: Gcx<'_>,
256    id: hir::ContractId,
257    current_contract: Option<hir::ContractId>,
258) -> MemberListOwned<'_> {
259    let contract = gcx.hir.contract(id);
260    let access = ContractTypeAccess::new(gcx, id, current_contract);
261    let mut members = Vec::new();
262
263    match access {
264        ContractTypeAccess::External => {
265            members.extend(gcx.interface_functions(id).iter().map(|f| {
266                let item = hir::ItemId::from(f.id);
267                let ty = declaration_function_ty(gcx, gcx.type_of_item(item));
268                Member::with_res(gcx.item_name(item).name, ty, item)
269            }));
270        }
271        ContractTypeAccess::Library | ContractTypeAccess::DerivingScope { .. } => {
272            members.extend(contract.functions().filter_map(|id| {
273                let ty = contract_type_own_function(gcx, id, access)?;
274                let item = hir::ItemId::from(id);
275                Some(Member::with_res(gcx.item_name(item).name, ty, item))
276            }));
277        }
278    }
279
280    members.extend(contract.items.iter().copied().filter_map(|item| {
281        if matches!(item, hir::ItemId::Function(_))
282            || !contract_type_item_visible(gcx.hir.item(item), access)
283        {
284            return None;
285        }
286        Some(Member::with_res(gcx.item_name(item).name, gcx.type_of_res(item.into()), item))
287    }));
288
289    members
290}
291
292#[derive(Clone, Copy)]
293enum ContractTypeAccess {
294    Library,
295    DerivingScope { same_contract: bool },
296    External,
297}
298
299impl ContractTypeAccess {
300    fn new(gcx: Gcx<'_>, id: hir::ContractId, current_contract: Option<hir::ContractId>) -> Self {
301        let contract = gcx.hir.contract(id);
302        if contract.kind.is_library() {
303            Self::Library
304        } else if let Some(current) = current_contract
305            && gcx.hir.contract(current).linearized_bases.contains(&id)
306        {
307            Self::DerivingScope { same_contract: current == id }
308        } else {
309            Self::External
310        }
311    }
312}
313
314fn contract_type_own_function<'gcx>(
315    gcx: Gcx<'gcx>,
316    id: hir::FunctionId,
317    access: ContractTypeAccess,
318) -> Option<Ty<'gcx>> {
319    let f = gcx.hir.function(id);
320    if !f.is_ordinary() {
321        return None;
322    }
323
324    match access {
325        ContractTypeAccess::Library if f.visibility >= Visibility::Internal => {
326            let ty = gcx.type_of_item(id.into());
327            Some(if f.visibility >= Visibility::Public {
328                ty.as_externally_callable_function(true, gcx)
329            } else {
330                ty
331            })
332        }
333        ContractTypeAccess::DerivingScope { same_contract }
334            if f.visibility > Visibility::Private && !f.is_getter() =>
335        {
336            let ty = gcx.type_of_item(id.into());
337            Some(
338                if matches!(f.visibility, Visibility::Internal | Visibility::Public)
339                    && f.body.is_some()
340                {
341                    if !same_contract && f.visibility >= Visibility::Public {
342                        internal_function_with_selector(gcx, ty)
343                    } else {
344                        ty
345                    }
346                } else {
347                    declaration_function_ty(gcx, ty)
348                },
349            )
350        }
351        _ => None,
352    }
353}
354
355fn declaration_function_ty<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> Ty<'gcx> {
356    let TyKind::Fn(fn_ty) = ty.kind else { unreachable!() };
357    gcx.mk_ty_fn(TyFn {
358        kind: TyFnKind::Declaration,
359        parameters: fn_ty.parameters,
360        returns: fn_ty.returns,
361        state_mutability: fn_ty.state_mutability,
362        function_id: fn_ty.function_id,
363        attached: false,
364    })
365}
366
367fn internal_function_with_selector<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> Ty<'gcx> {
368    let TyKind::Fn(fn_ty) = ty.kind else { unreachable!() };
369    gcx.mk_ty_fn(TyFn {
370        kind: TyFnKind::InternalWithSelector,
371        parameters: fn_ty.parameters,
372        returns: fn_ty.returns,
373        state_mutability: fn_ty.state_mutability,
374        function_id: fn_ty.function_id,
375        attached: false,
376    })
377}
378
379fn contract_type_item_visible(item: hir::Item<'_, '_>, access: ContractTypeAccess) -> bool {
380    match access {
381        ContractTypeAccess::Library => {
382            item.is_visible_as_library_member() || item.is_visible_via_contract_type_access()
383        }
384        ContractTypeAccess::DerivingScope { .. } => {
385            item.is_visible_via_contract_type_access()
386                || (matches!(item, hir::Item::Variable(_))
387                    && item.visibility() > Visibility::Private)
388        }
389        ContractTypeAccess::External => item.is_visible_via_contract_type_access(),
390    }
391}
392
393fn super_type(gcx: Gcx<'_>, id: hir::ContractId) -> MemberListOwned<'_> {
394    let mut members = Vec::new();
395    for &base in gcx.hir.contract(id).linearized_bases.iter().skip(1) {
396        for function in gcx.hir.contract(base).functions() {
397            let f = gcx.hir.function(function);
398            if !f.is_ordinary()
399                || f.visibility <= Visibility::Private
400                || f.visibility == Visibility::External
401                || f.body.is_none()
402            {
403                continue;
404            }
405
406            let item = hir::ItemId::from(function);
407            let name = gcx.item_name(item).name;
408            let params = gcx.item_parameter_types(item);
409            // Keep only the most-derived implementation for each override signature.
410            if members.iter().any(|member: &Member<'_>| match member.res {
411                Some(hir::Res::Item(item)) => {
412                    member.name == name && gcx.item_parameter_types(item) == params
413                }
414                _ => false,
415            }) {
416                continue;
417            }
418            let ty = gcx.type_of_item(item);
419            let ty = if f.visibility >= Visibility::Public {
420                internal_function_with_selector(gcx, ty)
421            } else {
422                ty
423            };
424            members.push(Member::with_res(name, ty, item));
425        }
426    }
427    members
428}
429
430pub(crate) fn internal_function_members_in_context<'gcx>(
431    gcx: Gcx<'gcx>,
432    id: hir::FunctionId,
433    current_contract: hir::ContractId,
434) -> MemberListOwned<'gcx> {
435    // Solc exposes `.selector` on internal function types only from a derived contract scope.
436    let function = gcx.hir.function(id);
437    let Some(defining_contract) = function.contract else {
438        return MemberListOwned::default();
439    };
440    if current_contract == defining_contract
441        || !function.is_part_of_external_interface()
442        || !gcx.hir.contract(current_contract).linearized_bases.contains(&defining_contract)
443    {
444        return MemberListOwned::default();
445    }
446    Member::of_builtins(gcx, [Builtin::FunctionSelector])
447}
448
449// `type(T)`
450fn meta<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> MemberListOwned<'gcx> {
451    match ty.kind {
452        TyKind::Contract(id) => {
453            if gcx.hir.contract(id).can_be_deployed() {
454                type_contract(gcx)
455            } else {
456                type_interface(gcx)
457            }
458        }
459        TyKind::Elementary(ElementaryType::Int(_) | ElementaryType::UInt(_)) | TyKind::Enum(_) => {
460            vec![
461                Member::with_builtin(Builtin::TypeMin, ty),
462                Member::with_builtin(Builtin::TypeMax, ty),
463            ]
464        }
465        _ => Default::default(),
466    }
467}
468
469fn array(gcx: Gcx<'_>) -> MemberListOwned<'_> {
470    Member::of_builtins(gcx, [Builtin::ArrayLength])
471}
472
473fn string_ty(gcx: Gcx<'_>) -> MemberListOwned<'_> {
474    Member::of_builtins(gcx, [Builtin::StringConcat])
475}
476
477fn bytes_ty(gcx: Gcx<'_>) -> MemberListOwned<'_> {
478    Member::of_builtins(gcx, [Builtin::BytesConcat])
479}
480
481fn type_contract(gcx: Gcx<'_>) -> MemberListOwned<'_> {
482    Member::of_builtins(
483        gcx,
484        [Builtin::ContractCreationCode, Builtin::ContractRuntimeCode, Builtin::ContractName],
485    )
486}
487
488fn type_interface(gcx: Gcx<'_>) -> MemberListOwned<'_> {
489    Member::of_builtins(gcx, [Builtin::InterfaceId, Builtin::ContractName])
490}