1use crate::linter::LintContext;
2use solar::{
3 ast::{
4 DataLocation, ElementaryType, ItemKind as AstItemKind, LitKind, StateMutability, StrKind,
5 TypeSize, UsingList as AstUsingList, Visibility,
6 },
7 interface::sym,
8 sema::{
9 Gcx, Ty,
10 hir::{
11 Block, CallArgs, CallArgsKind, ContractId, Expr, ExprKind, Function, FunctionId,
12 FunctionKind, Hir, ItemId, Modifier, Res, Stmt, StmtKind, VariableId, Visit,
13 },
14 ty::TyKind,
15 },
16};
17use std::ops::ControlFlow;
18
19pub(super) fn visit_payable_loop_expressions<'ctx, 's, 'hir, 'cb>(
20 ctx: &'ctx LintContext<'s, 'ctx>,
21 gcx: Gcx<'hir>,
22 hir: &'hir Hir<'hir>,
23 func: &'hir Function<'hir>,
24 f: impl FnMut(&'ctx LintContext<'s, 'ctx>, Gcx<'hir>, &'hir Hir<'hir>, &'hir Expr<'hir>) + 'cb,
25) {
26 if !is_payable_entry_point(func) {
27 return;
28 }
29
30 visit_loop_statements_and_expressions_with_options(
31 ctx,
32 gcx,
33 hir,
34 func,
35 true,
36 true,
37 |_, _, _, _| {},
38 f,
39 );
40}
41
42pub(super) fn visit_loop_statements_and_expressions<'ctx, 's, 'hir, 'cb>(
43 ctx: &'ctx LintContext<'s, 'ctx>,
44 gcx: Gcx<'hir>,
45 hir: &'hir Hir<'hir>,
46 func: &'hir Function<'hir>,
47 mut stmt_f: impl FnMut(&'ctx LintContext<'s, 'ctx>, Gcx<'hir>, &'hir Hir<'hir>, &'hir Stmt<'hir>)
48 + 'cb,
49 mut expr_f: impl FnMut(&'ctx LintContext<'s, 'ctx>, Gcx<'hir>, &'hir Hir<'hir>, &'hir Expr<'hir>)
50 + 'cb,
51) {
52 visit_loop_statements_and_expressions_with_options(
53 ctx,
54 gcx,
55 hir,
56 func,
57 false,
58 false,
59 &mut stmt_f,
60 &mut expr_f,
61 );
62}
63
64#[allow(clippy::too_many_arguments)]
65fn visit_loop_statements_and_expressions_with_options<'ctx, 's, 'hir, 'cb>(
66 ctx: &'ctx LintContext<'s, 'ctx>,
67 gcx: Gcx<'hir>,
68 hir: &'hir Hir<'hir>,
69 func: &'hir Function<'hir>,
70 follow_calls_outside_loop: bool,
71 report_local_loops_in_internal_calls: bool,
72 mut stmt_f: impl FnMut(&'ctx LintContext<'s, 'ctx>, Gcx<'hir>, &'hir Hir<'hir>, &'hir Stmt<'hir>)
73 + 'cb,
74 mut expr_f: impl FnMut(&'ctx LintContext<'s, 'ctx>, Gcx<'hir>, &'hir Hir<'hir>, &'hir Expr<'hir>)
75 + 'cb,
76) {
77 let Some(body) = func.body else { return };
78
79 let mut checker = LoopContextChecker {
80 ctx,
81 hir,
82 gcx,
83 loop_depth: 0,
84 placeholder: None,
85 modifier_stack: Vec::new(),
86 call_stack: Vec::new(),
87 internal_call_loop_depths: Vec::new(),
88 dispatch_contract: func.contract,
89 current_contract: func.contract,
90 follow_calls_outside_loop,
91 report_local_loops_in_internal_calls,
92 stmt_f: &mut stmt_f,
93 expr_f: &mut expr_f,
94 };
95 checker.visit_modifier_chain(func.modifiers, 0, body, func.contract);
96}
97
98fn is_payable_entry_point(func: &Function<'_>) -> bool {
99 !matches!(func.kind, FunctionKind::Constructor | FunctionKind::Modifier)
100 && func.state_mutability == StateMutability::Payable
101 && matches!(func.visibility, Visibility::Public | Visibility::External)
102}
103
104type LoopExprCallback<'ctx, 's, 'hir, 'cb> =
105 dyn FnMut(&'ctx LintContext<'s, 'ctx>, Gcx<'hir>, &'hir Hir<'hir>, &'hir Expr<'hir>) + 'cb;
106type LoopStmtCallback<'ctx, 's, 'hir, 'cb> =
107 dyn FnMut(&'ctx LintContext<'s, 'ctx>, Gcx<'hir>, &'hir Hir<'hir>, &'hir Stmt<'hir>) + 'cb;
108
109struct LoopContextChecker<'ctx, 's, 'hir, 'cb> {
110 ctx: &'ctx LintContext<'s, 'ctx>,
111 hir: &'hir Hir<'hir>,
112 gcx: Gcx<'hir>,
113 loop_depth: usize,
114 placeholder: Option<ModifierContinuation<'hir>>,
115 modifier_stack: Vec<FunctionId>,
116 call_stack: Vec<FunctionId>,
117 internal_call_loop_depths: Vec<usize>,
118 dispatch_contract: Option<ContractId>,
119 current_contract: Option<ContractId>,
120 follow_calls_outside_loop: bool,
121 report_local_loops_in_internal_calls: bool,
122 stmt_f: &'cb mut LoopStmtCallback<'ctx, 's, 'hir, 'cb>,
123 expr_f: &'cb mut LoopExprCallback<'ctx, 's, 'hir, 'cb>,
124}
125
126type ModifierContinuation<'hir> = (&'hir [Modifier<'hir>], usize, Block<'hir>, Option<ContractId>);
127
128impl<'ctx, 's, 'hir, 'cb> LoopContextChecker<'ctx, 's, 'hir, 'cb> {
129 fn visit_modifier_chain(
130 &mut self,
131 modifiers: &'hir [Modifier<'hir>],
132 index: usize,
133 body: Block<'hir>,
134 body_contract: Option<ContractId>,
135 ) {
136 let Some(modifier) = modifiers.get(index) else {
137 self.visit_block_with_placeholder(body, None, body_contract);
138 return;
139 };
140
141 let _ = self.visit_call_args(&modifier.args);
142
143 let Some(modifier_id) = modifier.id.as_function() else {
144 self.visit_modifier_chain(modifiers, index + 1, body, body_contract);
145 return;
146 };
147
148 if self.modifier_stack.contains(&modifier_id) {
149 self.visit_modifier_chain(modifiers, index + 1, body, body_contract);
150 return;
151 }
152
153 let modifier_func = self.hir.function(modifier_id);
154 let Some(modifier_body) = modifier_func.body else {
155 self.visit_modifier_chain(modifiers, index + 1, body, body_contract);
156 return;
157 };
158
159 self.modifier_stack.push(modifier_id);
160 self.visit_block_with_placeholder(
161 modifier_body,
162 Some((modifiers, index + 1, body, body_contract)),
163 modifier_func.contract,
164 );
165 self.modifier_stack.pop();
166 }
167
168 fn visit_block_stmts(&mut self, block: Block<'hir>) {
169 for stmt in block.stmts {
170 let _ = self.visit_stmt(stmt);
171 }
172 }
173
174 fn visit_block_with_placeholder(
175 &mut self,
176 block: Block<'hir>,
177 placeholder: Option<ModifierContinuation<'hir>>,
178 current_contract: Option<ContractId>,
179 ) {
180 let previous = self.placeholder;
181 let previous_contract = self.current_contract;
182 self.placeholder = placeholder;
183 self.current_contract = current_contract;
184 self.visit_block_stmts(block);
185 self.current_contract = previous_contract;
186 self.placeholder = previous;
187 }
188
189 fn visit_loop_block(&mut self, block: Block<'hir>) -> ControlFlow<()> {
190 self.loop_depth += 1;
191 self.visit_block_stmts(block);
192 self.loop_depth -= 1;
193 ControlFlow::Continue(())
194 }
195
196 fn visit_internal_call(&mut self, func_id: FunctionId) {
197 if self.call_stack.contains(&func_id) {
198 return;
199 }
200
201 let func = self.hir.function(func_id);
202 let Some(body) = func.body else { return };
203
204 self.call_stack.push(func_id);
205 self.internal_call_loop_depths.push(self.loop_depth);
206 self.visit_modifier_chain(func.modifiers, 0, body, func.contract);
207 self.internal_call_loop_depths.pop();
208 self.call_stack.pop();
209 }
210
211 fn current_loop_context_is_reportable(&self) -> bool {
212 if self.loop_depth == 0 {
213 return false;
214 }
215 self.report_local_loops_in_internal_calls
216 || self.internal_call_loop_depths.last().is_none_or(|&depth| self.loop_depth <= depth)
217 }
218
219 fn resolved_internal_function_ids(
220 &self,
221 callee: &'hir Expr<'hir>,
222 args: &CallArgs<'hir>,
223 ) -> Vec<FunctionId> {
224 match &callee.peel_parens().kind {
225 ExprKind::Ident(reses) => unique(
226 reses
227 .iter()
228 .filter_map(|res| match res {
229 Res::Item(ItemId::Function(func_id)) => Some(*func_id),
230 _ => None,
231 })
232 .filter(|&func_id| self.is_followable_call(func_id, args)),
233 )
234 .into_iter()
235 .collect(),
236 ExprKind::Member(base, member) => {
237 unique(self.member_function_ids(base, member.name, args).into_iter())
238 .into_iter()
239 .collect()
240 }
241 _ => Vec::new(),
242 }
243 }
244
245 fn member_function_ids(
246 &self,
247 base: &'hir Expr<'hir>,
248 member_name: solar::interface::Symbol,
249 args: &CallArgs<'hir>,
250 ) -> Vec<FunctionId> {
251 if is_builtin(base, sym::super_) {
252 return self.super_function_ids(member_name, args);
253 }
254
255 let contract_functions = match &base.peel_parens().kind {
256 ExprKind::Ident(reses) => reses
257 .iter()
258 .filter_map(|res| match res {
259 Res::Item(ItemId::Contract(contract_id)) => Some(*contract_id),
260 _ => None,
261 })
262 .flat_map(|contract_id| self.contract_function_ids(contract_id, member_name))
263 .filter(|&func_id| self.is_followable_call(func_id, args))
264 .collect(),
265 _ => Vec::new(),
266 };
267 if !contract_functions.is_empty() {
268 return contract_functions;
269 }
270
271 let Some(base_ty) = expr_ty(self.gcx, self.hir, base) else { return Vec::new() };
272
273 if matches!(base_ty.peel_refs().kind, TyKind::Contract(_)) {
274 return self.library_extension_function_ids(member_name, args, base);
275 }
276
277 let member_functions: Vec<_> = self
278 .gcx
279 .members_of(base_ty, base_item_source(self.hir, base), base_contract(self.hir, base))
280 .filter(|member| member.name == member_name)
281 .filter_map(|member| match (member.res, member.ty.kind) {
282 (Some(Res::Item(ItemId::Function(func_id))), _) => Some(func_id),
283 (_, TyKind::Fn(func)) => func.function_id,
284 _ => None,
285 })
286 .filter(|&func_id| self.is_followable_member_call(func_id, args, base))
287 .collect();
288 if !member_functions.is_empty() {
289 return member_functions;
290 }
291
292 self.library_extension_function_ids(member_name, args, base)
293 }
294
295 fn super_function_ids(
296 &self,
297 member_name: solar::interface::Symbol,
298 args: &CallArgs<'hir>,
299 ) -> Vec<FunctionId> {
300 let (Some(dispatch_contract), Some(current_contract)) =
301 (self.dispatch_contract, self.current_contract)
302 else {
303 return Vec::new();
304 };
305
306 let linearized_bases = self.hir.contract(dispatch_contract).linearized_bases;
307 let Some(current_index) = linearized_bases.iter().position(|&id| id == current_contract)
308 else {
309 return Vec::new();
310 };
311
312 for &base_id in linearized_bases.iter().skip(current_index + 1) {
313 let funcs: Vec<_> = self
314 .contract_function_ids(base_id, member_name)
315 .into_iter()
316 .filter(|&func_id| self.is_followable_call(func_id, args))
317 .collect();
318 if !funcs.is_empty() {
319 return funcs;
320 }
321 }
322
323 Vec::new()
324 }
325
326 fn contract_function_ids(
327 &self,
328 contract_id: ContractId,
329 member_name: solar::interface::Symbol,
330 ) -> Vec<FunctionId> {
331 self.hir
332 .contract(contract_id)
333 .functions()
334 .filter(|&func_id| {
335 let func = self.hir.function(func_id);
336 func.name.is_some_and(|name| name.name == member_name)
337 })
338 .collect()
339 }
340
341 fn is_followable_call(&self, func_id: FunctionId, args: &CallArgs<'hir>) -> bool {
342 let func = self.hir.function(func_id);
343 is_current_context_helper(func)
344 && args_match_function(self.gcx, self.hir, args, func.parameters)
345 }
346
347 fn is_followable_member_call(
348 &self,
349 func_id: FunctionId,
350 args: &CallArgs<'hir>,
351 receiver: &Expr<'hir>,
352 ) -> bool {
353 let func = self.hir.function(func_id);
354 is_current_context_helper(func)
355 && (args_match_function(self.gcx, self.hir, args, func.parameters)
356 || args_match_extension_function(self.gcx, self.hir, args, receiver, func))
357 }
358
359 fn library_extension_function_ids(
360 &self,
361 member_name: solar::interface::Symbol,
362 args: &CallArgs<'hir>,
363 receiver: &Expr<'hir>,
364 ) -> Vec<FunctionId> {
365 self.hir
366 .function_ids()
367 .filter(|&func_id| {
368 let func = self.hir.function(func_id);
369 func.contract
370 .is_some_and(|contract_id| self.hir.contract(contract_id).kind.is_library())
371 && func.name.is_some_and(|name| name.name == member_name)
372 && self.using_allows_extension(func_id, member_name)
373 && self.is_followable_member_call(func_id, args, receiver)
374 })
375 .collect()
376 }
377
378 fn using_allows_extension(
379 &self,
380 func_id: FunctionId,
381 member_name: solar::interface::Symbol,
382 ) -> bool {
383 let func = self.hir.function(func_id);
384 let Some(library_id) = func.contract else { return false };
385 let library_name = self.hir.contract(library_id).name.name;
386
387 let current_contract = self.current_contract.map(|id| self.hir.contract(id));
388 let source_id = current_contract.map(|contract| contract.source).unwrap_or(func.source);
389 let Some(source) = self.gcx.sources.get(source_id).and_then(|source| source.ast.as_ref())
390 else {
391 return false;
392 };
393
394 source.items.iter().any(|item| {
395 if let AstItemKind::Using(using) = &item.kind {
396 return using_list_allows_extension(&using.list, library_name, member_name);
397 }
398
399 let Some(current_contract) = current_contract else { return false };
400 let AstItemKind::Contract(contract) = &item.kind else { return false };
401 if contract.name.name != current_contract.name.name
402 || !item.span.contains(current_contract.span)
403 {
404 return false;
405 }
406
407 contract.body.iter().any(|item| match &item.kind {
408 AstItemKind::Using(using) => {
409 using_list_allows_extension(&using.list, library_name, member_name)
410 }
411 _ => false,
412 })
413 })
414 }
415}
416
417impl<'hir> Visit<'hir> for LoopContextChecker<'_, '_, 'hir, '_> {
418 type BreakValue = ();
419
420 fn hir(&self) -> &'hir Hir<'hir> {
421 self.hir
422 }
423
424 fn visit_stmt(&mut self, stmt: &'hir Stmt<'hir>) -> ControlFlow<Self::BreakValue> {
425 if self.current_loop_context_is_reportable() {
426 (self.stmt_f)(self.ctx, self.gcx, self.hir, stmt);
427 }
428
429 match stmt.kind {
430 StmtKind::Loop(block, _) => self.visit_loop_block(block),
431 StmtKind::Placeholder => {
432 if let Some((modifiers, index, body, body_contract)) = self.placeholder {
433 self.visit_modifier_chain(modifiers, index, body, body_contract);
434 }
435 ControlFlow::Continue(())
436 }
437 _ => self.walk_stmt(stmt),
438 }
439 }
440
441 fn visit_expr(&mut self, expr: &'hir Expr<'hir>) -> ControlFlow<Self::BreakValue> {
442 let reportable_loop_context = self.current_loop_context_is_reportable();
443 if reportable_loop_context {
444 (self.expr_f)(self.ctx, self.gcx, self.hir, expr);
445 }
446
447 let result = self.walk_expr(expr);
448 if result.is_break() {
449 return result;
450 }
451
452 if (self.follow_calls_outside_loop || reportable_loop_context)
453 && let ExprKind::Call(callee, args, _) = &expr.kind
454 {
455 for func_id in self.resolved_internal_function_ids(callee, args) {
456 self.visit_internal_call(func_id);
457 }
458 }
459
460 ControlFlow::Continue(())
461 }
462}
463
464fn is_current_context_helper(func: &Function<'_>) -> bool {
465 func.kind.is_ordinary()
466 && matches!(
467 func.visibility,
468 Visibility::Public | Visibility::Internal | Visibility::Private
469 )
470}
471
472pub(super) fn is_builtin(expr: &Expr<'_>, symbol: solar::interface::Symbol) -> bool {
473 let ExprKind::Ident(reses) = &expr.peel_parens().kind else { return false };
474 let mut iter = reses.iter().filter(|res| !matches!(res, Res::Err(_)));
475 matches!(
476 (iter.next(), iter.next()),
477 (Some(Res::Builtin(builtin)), None) if builtin.name() == symbol
478 )
479}
480
481pub(super) fn is_this_or_super(expr: &Expr<'_>) -> bool {
482 is_builtin(expr, sym::this) || is_builtin(expr, sym::super_)
483}
484
485fn unique<T>(mut iter: impl Iterator<Item = T>) -> Option<T> {
486 let first = iter.next()?;
487 iter.next().is_none().then_some(first)
488}
489
490fn using_list_allows_extension(
491 using_list: &AstUsingList<'_>,
492 library_name: solar::interface::Symbol,
493 member_name: solar::interface::Symbol,
494) -> bool {
495 match using_list {
496 AstUsingList::Single(path) => {
497 path.segments().last().is_some_and(|id| id.name == library_name)
498 }
499 AstUsingList::Multiple(paths) => paths.iter().any(|(path, _)| {
500 let segments = path.segments();
501 matches!(
502 segments,
503 [.., library, member] if library.name == library_name && member.name == member_name
504 )
505 }),
506 }
507}
508
509fn args_match_function<'gcx>(
510 gcx: Gcx<'gcx>,
511 hir: &Hir<'gcx>,
512 args: &CallArgs<'gcx>,
513 params: &'gcx [VariableId],
514) -> bool {
515 if args.len() != params.len() {
516 return false;
517 }
518
519 match args.kind {
520 CallArgsKind::Unnamed(exprs) => {
521 exprs.iter().zip(params).all(|(arg, ¶m)| arg_matches_param(gcx, hir, arg, param))
522 }
523 CallArgsKind::Named(named_args) => named_args.iter().all(|arg| {
524 params
525 .iter()
526 .copied()
527 .find(|¶m| {
528 hir.variable(param).name.is_some_and(|name| name.name == arg.name.name)
529 })
530 .is_some_and(|param| arg_matches_param(gcx, hir, &arg.value, param))
531 }),
532 }
533}
534
535fn args_match_extension_function<'gcx>(
536 gcx: Gcx<'gcx>,
537 hir: &Hir<'gcx>,
538 args: &CallArgs<'gcx>,
539 receiver: &Expr<'gcx>,
540 func: &Function<'gcx>,
541) -> bool {
542 let Some(params) = func.parameters.split_first() else { return false };
543 let (self_param, params) = params;
544 args.len() == params.len()
545 && receiver_matches_param(gcx, hir, receiver, *self_param)
546 && args_match_function(gcx, hir, args, params)
547}
548
549fn receiver_matches_param<'gcx>(
550 gcx: Gcx<'gcx>,
551 hir: &Hir<'gcx>,
552 receiver: &Expr<'gcx>,
553 param: VariableId,
554) -> bool {
555 let Some(receiver_ty) = expr_ty(gcx, hir, receiver) else {
556 return true;
557 };
558 let param_var = hir.variable(param);
559 let param_ty = gcx.type_of_item(param.into()).with_loc_if_ref_opt(gcx, param_var.data_location);
560 receiver_ty.convert_implicit_to(param_ty, gcx)
561}
562
563fn arg_matches_param<'gcx>(
564 gcx: Gcx<'gcx>,
565 hir: &Hir<'gcx>,
566 arg: &Expr<'gcx>,
567 param: VariableId,
568) -> bool {
569 let Some(arg_ty) = expr_ty(gcx, hir, arg) else {
570 return true;
571 };
572 let param_var = hir.variable(param);
573 let param_ty = gcx.type_of_item(param.into()).with_loc_if_ref_opt(gcx, param_var.data_location);
574 arg_ty.convert_implicit_to(param_ty, gcx)
575}
576
577pub(super) fn expr_ty<'gcx>(
578 gcx: Gcx<'gcx>,
579 hir: &Hir<'gcx>,
580 expr: &Expr<'gcx>,
581) -> Option<Ty<'gcx>> {
582 match &expr.peel_parens().kind {
583 ExprKind::Array(_) => None,
584 ExprKind::Call(callee, args, _) => {
585 let callee_ty = expr_ty(gcx, hir, callee)?;
586 match callee_ty.kind {
587 TyKind::Fn(func) => fn_call_return_type(gcx, func.returns),
588 TyKind::Type(to) => Some(explicit_cast_ty(gcx, to, args)),
589 _ => None,
590 }
591 }
592 ExprKind::Ident(reses) => {
593 let res = unique(reses.iter().filter(|res| !matches!(res, Res::Err(_))).copied())?;
594 match res {
595 Res::Builtin(builtin)
596 if matches!(
597 builtin.name(),
598 solar::interface::sym::this | solar::interface::sym::super_
599 ) =>
600 {
601 None
602 }
603 Res::Item(ItemId::Variable(var_id)) => Some(
604 gcx.type_of_res(res)
605 .with_loc_if_ref_opt(gcx, variable_data_location(hir, var_id)),
606 ),
607 _ => Some(gcx.type_of_res(res)),
608 }
609 }
610 ExprKind::Index(lhs, index) => {
611 let lhs_ty = expr_ty(gcx, hir, lhs)?;
612 if let Some(index) = index
613 && !expr_ty(gcx, hir, index)?.convert_implicit_to(gcx.types.uint(256), gcx)
614 {
615 return None;
616 }
617 index_ty(gcx, lhs_ty)
618 }
619 ExprKind::Lit(lit) => Some(match &lit.kind {
620 LitKind::Str(StrKind::Hex, s, _) => {
621 let size = TypeSize::try_new_fb_bytes(s.as_byte_str().len().min(32) as u8)?;
622 gcx.types.fixed_bytes(size.bytes())
623 }
624 LitKind::Str(_, s, _) => gcx.mk_ty_string_literal(s.as_byte_str()),
625 LitKind::Number(int) => gcx.mk_ty_int_literal(false, int.bit_len() as _)?,
626 LitKind::Rational(_) | LitKind::Err(_) => return None,
627 LitKind::Address(_) => gcx.types.address,
628 LitKind::Bool(_) => gcx.types.bool,
629 }),
630 ExprKind::Member(base, member) => member_ty(gcx, hir, base, member.name),
631 ExprKind::New(ty) => {
632 let ty = gcx.type_of_hir_ty(ty);
633 Some(gcx.mk_ty(TyKind::Type(ty)))
634 }
635 ExprKind::Payable(inner) => {
636 let inner_ty = expr_ty(gcx, hir, inner)?;
637 inner_ty
638 .convert_explicit_to(gcx.types.address_payable, gcx)
639 .then_some(gcx.types.address_payable)
640 }
641 ExprKind::Slice(lhs, ..) => {
642 let lhs_ty = expr_ty(gcx, hir, lhs)?;
643 lhs_ty.is_sliceable().then_some(gcx.mk_ty(TyKind::Slice(lhs_ty)))
644 }
645 ExprKind::Tuple(exprs) => {
646 let tys = exprs
647 .iter()
648 .map(|expr| expr.and_then(|expr| expr_ty(gcx, hir, expr)))
649 .collect::<Option<Vec<_>>>()?;
650 Some(gcx.mk_ty_tuple(gcx.mk_tys(&tys)))
651 }
652 ExprKind::Ternary(_, true_expr, false_expr) => {
653 let true_ty = expr_ty(gcx, hir, true_expr)?;
654 let false_ty = expr_ty(gcx, hir, false_expr)?;
655 common_ty(gcx, true_ty, false_ty)
656 }
657 ExprKind::Type(ty) | ExprKind::TypeCall(ty) => {
658 let ty = gcx.type_of_hir_ty(ty);
659 Some(gcx.mk_ty(TyKind::Type(ty)))
660 }
661 ExprKind::Unary(_, inner) => expr_ty(gcx, hir, inner),
662 ExprKind::Assign(..)
663 | ExprKind::Binary(..)
664 | ExprKind::Delete(..)
665 | ExprKind::YulMember(..)
666 | ExprKind::Err(_) => None,
667 }
668}
669
670fn common_ty<'gcx>(gcx: Gcx<'gcx>, lhs: Ty<'gcx>, rhs: Ty<'gcx>) -> Option<Ty<'gcx>> {
671 if lhs.convert_implicit_to(rhs, gcx) {
672 Some(rhs)
673 } else {
674 rhs.convert_implicit_to(lhs, gcx).then_some(lhs)
675 }
676}
677
678fn fn_call_return_type<'gcx>(gcx: Gcx<'gcx>, returns: &'gcx [Ty<'gcx>]) -> Option<Ty<'gcx>> {
679 Some(match returns {
680 [] => gcx.types.unit,
681 [ret] => *ret,
682 _ => gcx.mk_ty_tuple(returns),
683 })
684}
685
686fn explicit_cast_ty<'gcx>(gcx: Gcx<'gcx>, to: Ty<'gcx>, args: &CallArgs<'gcx>) -> Ty<'gcx> {
687 match args.exprs().next().and_then(|arg| expr_ty(gcx, &gcx.hir, arg)) {
688 Some(from) => from.try_convert_explicit_to(to, gcx).unwrap_or(to),
689 None => to,
690 }
691}
692
693fn index_ty<'gcx>(gcx: Gcx<'gcx>, base_ty: Ty<'gcx>) -> Option<Ty<'gcx>> {
694 let loc = indexed_base_data_location(base_ty);
695 match base_ty.peel_refs().kind {
696 TyKind::Mapping(_, value) => Some(value.with_loc_if_ref_opt(gcx, loc)),
697 _ => base_ty.base_type(gcx),
698 }
699}
700
701fn indexed_base_data_location(ty: Ty<'_>) -> Option<DataLocation> {
702 ty.loc().or_else(|| matches!(ty.kind, TyKind::Mapping(..)).then_some(DataLocation::Storage))
703}
704
705fn member_ty<'gcx>(
706 gcx: Gcx<'gcx>,
707 hir: &Hir<'gcx>,
708 base: &Expr<'gcx>,
709 member_name: solar::interface::Symbol,
710) -> Option<Ty<'gcx>> {
711 let base_ty = match &base.peel_parens().kind {
712 ExprKind::Ident(_) if is_this_or_super(base) => {
713 return None;
714 }
715 _ => expr_ty(gcx, hir, base)?,
716 };
717
718 unique(
719 gcx.members_of(base_ty, base_item_source(hir, base), base_contract(hir, base))
720 .filter(|member| member.name == member_name)
721 .map(|member| member.ty),
722 )
723}
724
725fn base_item_source(hir: &Hir<'_>, expr: &Expr<'_>) -> solar::sema::hir::SourceId {
726 referenced_item(expr)
727 .map(|id| hir.item(id).source())
728 .unwrap_or_else(|| hir.sources_enumerated().next().expect("HIR has a source").0)
729}
730
731fn base_contract(hir: &Hir<'_>, expr: &Expr<'_>) -> Option<solar::sema::hir::ContractId> {
732 referenced_item(expr).and_then(|id| hir.item(id).contract())
733}
734
735fn referenced_item(expr: &Expr<'_>) -> Option<ItemId> {
736 match &expr.peel_parens().kind {
737 ExprKind::Ident([Res::Item(id), ..]) => Some(*id),
738 _ => None,
739 }
740}
741
742fn variable_data_location(hir: &Hir<'_>, var_id: VariableId) -> Option<DataLocation> {
743 let var = hir.variable(var_id);
744 var.data_location.or_else(|| {
745 (var.parent.is_none() && var.contract.is_some()).then_some(DataLocation::Storage)
746 })
747}
748
749pub(super) fn is_address_ty(ty: Ty<'_>) -> bool {
750 matches!(ty.peel_refs().kind, TyKind::Elementary(ElementaryType::Address(_)))
751}