1use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
2
3use diagnostics::UnusedExpressionKind;
4use syntax::ast::{Expression, Generic, Pattern, Span, StructKind, UnaryOperator};
5use syntax::program::{Definition, ReceiverCoercion};
6use syntax::types::{Bound, Type, TypeVariableState};
7
8use crate::checker::PostInferenceCheck;
9
10use super::super::Checker;
11use super::expressions::patterns::collect_pattern_bindings;
12
13impl Checker<'_, '_> {
14 pub(crate) fn check_unused_expression(
15 &mut self,
16 span: Span,
17 ty: &Type,
18 is_literal: bool,
19 allowed_lints: &[String],
20 ) {
21 let kind = if is_literal {
22 Some(UnusedExpressionKind::Literal)
23 } else if ty.is_result() {
24 Some(UnusedExpressionKind::Result)
25 } else if ty.is_option() {
26 Some(UnusedExpressionKind::Option)
27 } else if ty.is_partial() {
28 Some(UnusedExpressionKind::Partial)
29 } else if !ty.is_unit() && !ty.is_variable() && !ty.is_never() {
30 Some(UnusedExpressionKind::Value)
31 } else {
32 None
33 };
34
35 if let Some(kind) = kind
36 && !allowed_lints.contains(&kind.lint_name().to_string())
37 {
38 self.facts.add_unused_expression(span, kind);
39 }
40 }
41
42 pub(crate) fn callee_allowed_lints(
43 &mut self,
44 callee_name: &str,
45 inferred: &Expression,
46 ) -> Vec<String> {
47 if let Some(qualified) = self.lookup_qualified_name(callee_name)
48 && let Some(definition) = self.store.get_definition(&qualified)
49 {
50 return definition.allowed_lints().to_vec();
51 }
52
53 if let Expression::Call { expression, .. } = inferred
54 && let Expression::DotAccess {
55 expression: receiver,
56 member,
57 ..
58 } = expression.as_ref()
59 {
60 let receiver_ty = receiver.get_type().resolve().strip_refs();
61 if let Type::Constructor { id, .. } = &receiver_ty {
62 let method_key = format!("{}.{}", id, member);
63 if let Some(definition) = self.store.get_definition(&method_key) {
64 return definition.allowed_lints().to_vec();
65 }
66 }
67 }
68
69 vec![]
70 }
71
72 pub(crate) fn get_call_return_type(expression: &Expression) -> Option<Type> {
73 if let Expression::Call { expression, .. } = expression {
74 let callee_ty = expression.get_type().resolve();
75 match callee_ty {
76 Type::Function { return_type, .. } => Some(return_type.as_ref().clone()),
77 _ => None,
78 }
79 } else {
80 None
81 }
82 }
83
84 pub(crate) fn is_channel_send(expression: &Expression) -> bool {
85 if let Expression::Call { expression, .. } = expression
86 && let Expression::DotAccess {
87 expression: receiver,
88 member,
89 ..
90 } = expression.as_ref()
91 && member == "send"
92 {
93 return receiver
94 .get_type()
95 .resolve()
96 .get_name()
97 .map(|n| n == "Channel" || n == "Sender")
98 .unwrap_or(false);
99 }
100 false
101 }
102
103 pub(crate) fn check_unused_type_parameters(&mut self, generics: &[Generic], fn_ty: &Type) {
104 if generics.is_empty() {
105 return;
106 }
107
108 let mut remaining: HashSet<_> = generics.iter().map(|g| g.name.clone()).collect();
109 fn_ty.remove_found_type_names(&mut remaining);
110
111 let is_typedef = self.is_d_lis();
112 for generic in generics {
113 if generic.name.starts_with('_') {
114 continue;
115 }
116
117 if remaining.contains(&generic.name) {
118 self.facts.add_unused_type_param(
119 generic.name.to_string(),
120 generic.span,
121 is_typedef,
122 );
123 }
124 }
125 }
126
127 pub(crate) fn check_prelude_shadowing(&mut self, name: &str, span: Span) {
128 if self.is_d_lis() {
129 return;
130 }
131 let prelude_qualified_name = format!("prelude.{}", name);
132 if let Some(prelude_module) = self.store.get_module("prelude")
133 && prelude_module
134 .definitions
135 .contains_key(prelude_qualified_name.as_str())
136 {
137 self.sink
138 .push(diagnostics::infer::prelude_type_shadowed(name, span));
139 }
140 }
141
142 pub(crate) fn check_slice_index_type(
144 &mut self,
145 type_name: &str,
146 index_ty: &Type,
147 span: Span,
148 ) -> bool {
149 if type_name != "Slice" || index_ty.is_variable() {
150 return true;
151 }
152
153 if index_ty.get_name().is_some_and(|n| n == "int") {
154 return true;
155 }
156
157 self.sink
158 .push(diagnostics::infer::slice_index_type_mismatch(
159 index_ty, span,
160 ));
161 false
162 }
163
164 pub(crate) fn check_call_arity(
165 &mut self,
166 param_types: &[Type],
167 args: &[Expression],
168 callee_expression: &Expression,
169 span: &Span,
170 ) {
171 if param_types.len() != args.len() {
172 let actual_types: Vec<Type> = args.iter().map(|e| e.get_type()).collect();
173 let generic_params = self.get_generic_param_names(callee_expression);
174 let is_constructor = callee_expression
175 .get_var_name()
176 .map(|name| name.chars().next().is_some_and(|c| c.is_uppercase()))
177 .unwrap_or(false);
178 self.sink.push(diagnostics::infer::arity_mismatch(
179 param_types,
180 &actual_types,
181 &generic_params,
182 is_constructor,
183 *span,
184 ));
185 }
186 }
187
188 fn get_generic_param_names(&self, expression: &Expression) -> Vec<String> {
189 if let Expression::Identifier { value, .. } = expression
190 && let Some(ty) = self.scopes.lookup_value(value)
191 {
192 return match ty {
193 Type::Forall { vars, .. } => vars.iter().map(|s| s.to_string()).collect(),
194 _ => vec![],
195 };
196 }
197 vec![]
198 }
199
200 pub(crate) fn check_unconstrained_bounded_type_params(
201 &mut self,
202 bounds: &[Bound],
203 span: &Span,
204 ) {
205 for bound in bounds {
206 if let Type::Variable(var) = &bound.generic.resolve()
207 && let TypeVariableState::Unbound { .. } = &*var.borrow()
208 {
209 self.sink.push(diagnostics::infer::unconstrained_type_param(
210 &bound.param_name,
211 *span,
212 ));
213 }
214 }
215 }
216
217 pub fn run_post_inference_checks(&mut self) {
221 for check in std::mem::take(&mut self.post_inference_checks) {
222 match check {
223 PostInferenceCheck::GenericCall { return_ty, span } => {
224 if return_ty.resolve().has_unbound_variables() {
225 self.sink
226 .push(diagnostics::infer::cannot_infer_type_argument(span));
227 }
228 }
229 PostInferenceCheck::EmptyCollection { name, ty, span } => {
230 if ty.resolve().has_unbound_variables() {
231 self.sink
232 .push(diagnostics::infer::uninferred_binding(&name, span));
233 }
234 }
235 PostInferenceCheck::StatementTail { expected_ty, span } => {
236 let resolved = expected_ty.resolve();
237 if !resolved.is_unit()
238 && !matches!(resolved, Type::Variable(_))
239 && !expected_ty.is_ignored()
240 {
241 self.sink.push(diagnostics::infer::statement_as_tail(span));
242 }
243 }
244 }
245 }
246 }
247
248 pub(crate) fn check_constrained_return_type(
249 &mut self,
250 return_ty: &Type,
251 generics: &[Generic],
252 span: &Span,
253 fn_name: &str,
254 ) {
255 let Type::Constructor { id, params, .. } = return_ty else {
256 return;
257 };
258
259 if params.is_empty() {
260 return;
261 }
262
263 let qualified_id = self
264 .lookup_qualified_name(id)
265 .unwrap_or_else(|| id.as_ref().to_string());
266 let Some(definition) = self.store.get_definition(&qualified_id) else {
267 return;
268 };
269
270 let methods = match definition {
271 syntax::program::Definition::Struct { methods, .. } => methods,
272 syntax::program::Definition::Enum { methods, .. } => methods,
273 _ => return,
274 };
275
276 let mut required_bounds: HashMap<String, Vec<Type>> = HashMap::default();
277
278 for method_ty in methods.values() {
279 if let Type::Forall { vars: _, body } = method_ty {
280 if let Type::Function { bounds, .. } = body.as_ref() {
281 for bound in bounds {
282 if let Type::Parameter(param_name) = &bound.generic {
283 let entry = required_bounds
284 .entry(param_name.as_ref().to_string())
285 .or_default();
286 if !entry.contains(&bound.ty) {
287 entry.push(bound.ty.clone());
288 }
289 }
290 }
291 }
292 } else if let Type::Function { bounds, .. } = method_ty {
293 for bound in bounds {
294 if let Type::Parameter(param_name) = &bound.generic {
295 let entry = required_bounds
296 .entry(param_name.as_ref().to_string())
297 .or_default();
298 if !entry.contains(&bound.ty) {
299 entry.push(bound.ty.clone());
300 }
301 }
302 }
303 }
304 }
305
306 for return_param in params.iter() {
307 if let Type::Parameter(param_name) = return_param
308 && let Some(method_bounds) = required_bounds.get(param_name.as_ref())
309 {
310 let fn_generic = generics
311 .iter()
312 .find(|g| g.name.as_ref() == param_name.as_ref());
313
314 if let Some(fn_gen) = fn_generic {
315 let fn_bounds: Vec<Type> = fn_gen
316 .bounds
317 .iter()
318 .map(|b| self.convert_to_type(b, span))
319 .collect();
320
321 for method_bound in method_bounds {
322 if !fn_bounds.iter().any(|fb| fb == method_bound) {
323 self.sink.push(
324 diagnostics::infer::missing_constraint_on_generic_return_type(
325 fn_name,
326 param_name.as_ref(),
327 method_bound,
328 *span,
329 ),
330 );
331 }
332 }
333 } else {
334 let scope_bounds = self.scopes.collect_all_trait_bounds();
336 let qualified = self.qualify_name(param_name.as_ref());
337 let impl_bounds = scope_bounds
338 .get(qualified.as_str())
339 .or_else(|| scope_bounds.get(param_name.as_ref()));
340
341 let all_covered = impl_bounds
342 .is_some_and(|ib| method_bounds.iter().all(|mb| ib.contains(mb)));
343
344 if !all_covered {
345 let bound_str = method_bounds
346 .iter()
347 .map(|b| b.to_string())
348 .collect::<Vec<_>>()
349 .join(" + ");
350
351 self.sink.push(
352 diagnostics::infer::missing_constraint_on_generic_return_type(
353 fn_name,
354 param_name.as_ref(),
355 &Type::Parameter(bound_str.into()),
356 *span,
357 ),
358 );
359 }
360 }
361 }
362 }
363 }
364
365 pub(crate) fn check_newtype_field_assignment(&mut self, target: &Expression, span: Span) {
369 match target {
370 Expression::DotAccess {
371 expression, member, ..
372 } => {
373 if member == "0" {
374 let base_ty = expression.get_type().resolve();
375 let ty = base_ty.strip_refs();
376 if let Type::Constructor { id, params, .. } = ty
377 && params.is_empty()
378 && let Some(Definition::Struct {
379 kind: StructKind::Tuple,
380 fields,
381 generics,
382 ..
383 }) = self.store.get_definition(id.as_str())
384 && fields.len() == 1
385 && generics.is_empty()
386 {
387 let type_name = id.rsplit('.').next().unwrap_or(id.as_str());
388 self.sink.push(diagnostics::infer::newtype_field_assignment(
389 type_name, span,
390 ));
391 return;
392 }
393 }
394 self.check_newtype_field_assignment(expression, span);
396 }
397 Expression::IndexedAccess { expression, .. } => {
398 self.check_newtype_field_assignment(expression, span);
399 }
400 Expression::Unary {
401 operator: UnaryOperator::Deref,
402 expression,
403 ..
404 } => {
405 self.check_newtype_field_assignment(expression, span);
406 }
407 _ => {}
408 }
409 }
410
411 pub(crate) fn check_map_field_chain_assignment(&mut self, target: &Expression, span: Span) {
415 if self.has_map_field_in_chain(target) {
416 self.sink
417 .push(diagnostics::infer::map_field_chain_assignment(span));
418 }
419 }
420
421 pub(crate) fn has_map_field_in_chain(&self, expression: &Expression) -> bool {
422 match expression.unwrap_parens() {
423 Expression::DotAccess { expression, .. } => {
424 self.is_map_indexed_access(expression) || self.has_map_field_in_chain(expression)
425 }
426 _ => false,
427 }
428 }
429
430 fn is_map_indexed_access(&self, expression: &Expression) -> bool {
431 match expression.unwrap_parens() {
432 Expression::IndexedAccess { expression, .. } => {
433 expression.get_type().resolve().has_name("Map")
434 }
435 _ => false,
436 }
437 }
438
439 pub(crate) fn check_native_value_usage(&mut self, value: &str, ty: &Type, span: Span) {
442 if matches!(
443 value,
444 "imaginary" | "assert_type" | "complex" | "real" | "panic"
445 ) {
446 let qualified = format!("{}.{}", self.cursor.module_id, value);
447 if self.store.get_definition(&qualified).is_none() {
448 self.sink
449 .push(diagnostics::infer::native_constructor_value(value, span));
450 return;
451 }
452 }
453
454 {
455 let qualified = if value.contains('.') {
456 value.to_string()
457 } else {
458 format!("{}.{}", self.cursor.module_id, value)
459 };
460 let is_tuple_struct = match self.store.get_definition(&qualified) {
461 Some(Definition::Struct {
462 kind: StructKind::Tuple,
463 ..
464 }) => true,
465 Some(Definition::TypeAlias { ty: alias_ty, .. }) => {
466 if let Type::Constructor { id, .. } = alias_ty.unwrap_forall() {
467 matches!(
468 self.store.get_definition(id),
469 Some(Definition::Struct {
470 kind: StructKind::Tuple,
471 ..
472 })
473 )
474 } else {
475 false
476 }
477 }
478 _ => false,
479 };
480 if is_tuple_struct {
481 self.sink
482 .push(diagnostics::infer::native_constructor_value(value, span));
483 return;
484 }
485 }
486
487 let Some((type_part, method_part)) = value.split_once('.') else {
488 return;
489 };
490 if method_part.contains('.') {
491 return;
492 }
493
494 let is_native = matches!(
495 type_part,
496 "Slice" | "EnumeratedSlice" | "Map" | "Channel" | "Sender" | "Receiver" | "string"
497 );
498
499 if is_native {
500 if matches!(method_part, "new" | "buffered") {
501 self.sink
502 .push(diagnostics::infer::native_constructor_value(value, span));
503 } else {
504 self.sink
505 .push(diagnostics::infer::native_method_value(method_part, span));
506 }
507 return;
508 }
509
510 if matches!(method_part, "new" | "buffered") {
511 let ret_ty = match ty {
512 Type::Function { return_type, .. } => Some(return_type.as_ref()),
513 Type::Forall { body, .. } => match body.as_ref() {
514 Type::Function { return_type, .. } => Some(return_type.as_ref()),
515 _ => None,
516 },
517 _ => None,
518 };
519 if let Some(ret) = ret_ty {
520 let resolved = ret.resolve();
521 let is_native_ret =
522 matches!(resolved.get_name(), Some("Channel" | "Map" | "Slice"));
523 if is_native_ret {
524 self.sink
525 .push(diagnostics::infer::native_constructor_value(value, span));
526 return;
527 }
528 }
529 }
530
531 let is_fn = matches!(ty, Type::Function { .. } | Type::Forall { .. });
532 if !is_fn {
533 return;
534 }
535 let fn_params = match ty {
536 Type::Function { params, .. } => params.as_slice(),
537 Type::Forall { body, .. } => match body.as_ref() {
538 Type::Function { params, .. } => params.as_slice(),
539 _ => return,
540 },
541 _ => return,
542 };
543 let Some(first) = fn_params.first() else {
544 return;
545 };
546 let stripped = first.strip_refs();
547 let is_self = matches!(&stripped, Type::Constructor { id, .. }
548 if id.rsplit('.').next() == Some(type_part));
549 if !is_self {
550 return;
551 }
552
553 let module_id = &self.cursor.module_id;
555 let method_key = format!("{}.{}.{}", module_id, type_part, method_part);
556 let is_public = self
557 .store
558 .get_definition(&method_key)
559 .map(|d| d.visibility().is_public())
560 .unwrap_or(true);
561
562 if !is_public {
563 self.sink
564 .push(diagnostics::infer::private_method_expression(span));
565 }
566 }
567
568 pub(crate) fn has_newtype_dot0_in_chain(&self, expression: &Expression) -> bool {
569 let mut current = expression.unwrap_parens();
570 while let Expression::DotAccess {
571 expression: inner,
572 member,
573 ..
574 } = current
575 {
576 if member.parse::<usize>().is_ok() {
577 let ty = inner.get_type().resolve().strip_refs();
578 if let Type::Constructor { id, .. } = &ty
579 && let Some(Definition::Struct {
580 kind: StructKind::Tuple,
581 fields,
582 generics,
583 ..
584 }) = self.store.get_definition(id.as_str())
585 && fields.len() == 1
586 && generics.is_empty()
587 {
588 return true;
589 }
590 }
591 current = inner.unwrap_parens();
592 }
593 false
594 }
595
596 pub(crate) fn check_not_temp_producing(&mut self, expression: &Expression) {
597 if is_temp_producing(expression) || self.has_auto_address_on_call(expression) {
598 self.sink.push(diagnostics::infer::complex_sub_expression(
599 expression.get_span(),
600 ));
601 }
602 }
603
604 fn has_auto_address_on_call(&self, expression: &Expression) -> bool {
609 let expression = expression.unwrap_parens();
610 if let Expression::Call { expression, .. } = expression
611 && let Expression::DotAccess {
612 expression: receiver,
613 ..
614 } = expression.unwrap_parens()
615 {
616 if matches!(receiver.unwrap_parens(), Expression::Call { .. })
617 && self.coercions.get_coercion(receiver.get_span())
618 == Some(ReceiverCoercion::AutoAddress)
619 {
620 return true;
621 }
622 return self.has_auto_address_on_call(receiver);
623 }
624 false
625 }
626}
627
628pub(crate) fn is_temp_producing(expression: &Expression) -> bool {
629 matches!(
630 expression.unwrap_parens(),
631 Expression::If { .. }
632 | Expression::IfLet { .. }
633 | Expression::Match { .. }
634 | Expression::Block { .. }
635 | Expression::Loop { .. }
636 | Expression::Select { .. }
637 | Expression::TryBlock { .. }
638 | Expression::RecoverBlock { .. }
639 )
640}
641
642pub(crate) fn check_is_non_addressable(expression: &Expression) -> Option<&'static str> {
643 match expression {
644 Expression::Identifier { .. } => None,
645 Expression::DotAccess { expression, .. } => {
646 let inner = expression.unwrap_parens();
647 let is_non_addressable_origin = matches!(inner, Expression::StructCall { .. })
649 || (matches!(inner, Expression::Call { .. })
650 && !expression.get_type().resolve().is_ref());
651 if is_non_addressable_origin {
652 Some("field access on non-addressable value")
653 } else {
654 check_is_non_addressable(expression)
655 }
656 }
657 Expression::IndexedAccess { expression, .. } => {
658 let expression_ty = expression.get_type().resolve();
659 if let Some(name) = expression_ty.get_name() {
660 if name == "Map" {
661 return Some("map index expression");
662 }
663 if name == "Slice" {
665 return None;
666 }
667 }
668 if matches!(expression.unwrap_parens(), Expression::Call { .. }) {
669 Some("index access on function call")
670 } else {
671 check_is_non_addressable(expression)
672 }
673 }
674 Expression::Unary {
675 operator: UnaryOperator::Deref,
676 ..
677 } => None,
678 Expression::StructCall { .. } => None,
679 Expression::Paren { expression, .. } => check_is_non_addressable(expression),
680 Expression::Call { .. } => None,
681 Expression::Literal { .. } => Some("literal"),
682 Expression::Binary { .. } => Some("binary expression"),
683 Expression::If { .. } | Expression::IfLet { .. } => Some("conditional expression"),
684 Expression::Match { .. } => Some("match expression"),
685 Expression::Block { .. } => Some("block expression"),
686 Expression::Lambda { .. } => Some("lambda"),
687 Expression::Tuple { .. } => Some("tuple"),
688 Expression::Range { .. } => Some("range expression"),
689 _ => Some("expression"),
690 }
691}
692
693pub(crate) fn check_non_addressable_assignment_target(
697 expression: &Expression,
698) -> Option<&'static str> {
699 match expression.unwrap_parens() {
700 Expression::Identifier { .. } => None,
701 Expression::DotAccess { expression, .. } => {
702 if matches!(expression.unwrap_parens(), Expression::Call { .. })
704 && expression.get_type().resolve().is_ref()
705 {
706 None
707 } else {
708 check_non_addressable_assignment_target(expression)
709 }
710 }
711 Expression::IndexedAccess { .. } => None,
712 Expression::Unary {
713 operator: UnaryOperator::Deref,
714 ..
715 } => None,
716 Expression::Call { .. } => Some("function call result"),
717 Expression::StructCall { .. } => Some("struct literal"),
718 Expression::Tuple { .. } => Some("tuple literal"),
719 _ => None,
720 }
721}
722
723pub(crate) fn check_duplicate_bindings(sink: &diagnostics::DiagnosticSink, pattern: &Pattern) {
724 if let Pattern::Or { patterns, .. } = pattern {
725 for alternative_pattern in patterns {
726 check_duplicate_bindings(sink, alternative_pattern);
727 }
728 return;
729 }
730
731 if matches!(
732 pattern,
733 Pattern::Identifier { .. }
734 | Pattern::WildCard { .. }
735 | Pattern::Literal { .. }
736 | Pattern::Unit { .. }
737 ) {
738 return;
739 }
740
741 let bindings = collect_pattern_bindings(pattern);
742 let mut seen: HashMap<&str, &Span> = HashMap::default();
743 for (name, span) in &bindings {
744 if let Some(first_span) = seen.get(name.as_str()) {
745 sink.push(diagnostics::infer::duplicate_binding_in_pattern(
746 name,
747 *(*first_span),
748 *span,
749 ));
750 } else {
751 seen.insert(name, span);
752 }
753 }
754}
755
756pub(crate) fn check_binding_pattern(sink: &diagnostics::DiagnosticSink, pattern: &Pattern) {
757 if matches!(pattern, Pattern::Literal { .. }) {
758 sink.push(diagnostics::infer::literal_pattern_in_binding(
759 pattern.get_span(),
760 ));
761 }
762
763 if matches!(pattern, Pattern::Or { .. }) {
764 sink.push(diagnostics::infer::or_pattern_in_irrefutable_context(
765 pattern.get_span(),
766 ));
767 }
768}
769
770pub(crate) fn check_receiver(
771 sink: &diagnostics::DiagnosticSink,
772 method: &Expression,
773 impl_ty: &Type,
774) {
775 let Expression::Function { params, .. } = method else {
776 return;
777 };
778 let Some(first_param) = params.first() else {
779 return;
780 };
781 let Pattern::Identifier { identifier, span } = &first_param.pattern else {
782 return;
783 };
784
785 let receiver_ty = first_param.ty.strip_refs();
786 let types_match = receiver_ty == *impl_ty;
787
788 if types_match && identifier != "self" {
789 sink.push(diagnostics::infer::receiver_must_be_named_self(
790 identifier, *span,
791 ));
792 }
793
794 if !types_match && identifier == "self" {
795 let annotation_span = first_param
796 .annotation
797 .as_ref()
798 .map(|a| a.get_span())
799 .unwrap_or_else(|| *span);
800 let impl_type_name = impl_ty.get_name().unwrap_or_default();
801 let receiver_type_name = receiver_ty.get_name().unwrap_or_default();
802 sink.push(diagnostics::infer::receiver_type_mismatch(
803 impl_type_name,
804 receiver_type_name,
805 annotation_span,
806 ));
807 }
808}
809
810pub fn check_interface_visibility(
811 store: &crate::store::Store,
812 module_id: &str,
813 sink: &diagnostics::DiagnosticSink,
814) {
815 let module = match store.get_module(module_id) {
816 Some(m) => m,
817 None => return,
818 };
819
820 let non_pub_interfaces: HashMap<String, HashSet<String>> = module
821 .definitions
822 .iter()
823 .filter(|(key, _)| key.starts_with(&format!("{}.", module_id)))
824 .filter_map(|(_, definition)| {
825 if let syntax::program::Definition::Interface {
826 visibility: syntax::program::Visibility::Private,
827 definition: interface_data,
828 ..
829 } = definition
830 {
831 let method_names = interface_data
832 .methods
833 .keys()
834 .map(|k| k.to_string())
835 .collect();
836 Some((interface_data.name.to_string(), method_names))
837 } else {
838 None
839 }
840 })
841 .collect();
842
843 if non_pub_interfaces.is_empty() {
844 return;
845 }
846
847 for (_, definition) in module
848 .definitions
849 .iter()
850 .filter(|(key, _)| key.starts_with(&format!("{}.", module_id)))
851 {
852 if let syntax::program::Definition::Struct {
853 methods,
854 name,
855 name_span,
856 ..
857 } = definition
858 {
859 for method_name in methods.keys() {
860 for (interface_name, interface_methods) in &non_pub_interfaces {
861 if interface_methods.contains(method_name.as_str()) {
862 let method_key = format!("{}.{}.{}", module_id, name, method_name);
863 let method_is_pub = module
864 .definitions
865 .get(method_key.as_str())
866 .map(|definition| definition.visibility().is_public())
867 .unwrap_or(false);
868
869 if method_is_pub {
870 sink.push(diagnostics::infer::non_pub_interface_with_pub_impl(
871 interface_name,
872 name,
873 *name_span,
874 ));
875 return;
876 }
877 }
878 }
879 }
880 }
881 }
882}