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 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
228fn 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 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 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
449fn 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}