Skip to main content

lisette_semantics/
call_classification.rs

1use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
2
3use syntax::ast::Expression;
4use syntax::program::{DefinitionBody, Module};
5use syntax::types::{Symbol, Type};
6
7/// Determines if a method type requires UFCS emission based on its signature.
8///
9/// A method is UFCS when:
10/// - It has extra type parameters beyond the base type's generics (e.g., `Option<T>.map<U>`)
11/// - It has no Forall but the base type is generic (specialized impl block)
12/// - Its receiver has concrete type constructor parameters (e.g., `impl Option<int>`)
13pub fn is_ufcs_method_type(method_ty: &Type, base_generics_count: usize) -> bool {
14    let Type::Forall { vars, body } = method_ty else {
15        return base_generics_count > 0;
16    };
17
18    if vars.len() > base_generics_count {
19        return true;
20    }
21
22    if let Type::Function { params, .. } = body.as_ref()
23        && let Some(receiver_param) = params.first()
24        && let Type::Nominal {
25            params: receiver_params,
26            ..
27        } = receiver_param.strip_refs()
28    {
29        for param in receiver_params {
30            if matches!(param, Type::Nominal { .. }) {
31                return true;
32            }
33        }
34    }
35
36    false
37}
38
39/// Compute UFCS methods for a single module's types.
40///
41/// Three conditions (any one suffices):
42/// 1. Extra type params: method's Forall vars exceed base type's generics count
43/// 2. Specialized receiver: receiver's type constructor params contain concrete types
44/// 3. Mixed impl blocks: type has both bounded and unbounded impl blocks
45pub fn compute_module_ufcs(module: &Module, module_id: &str) -> Vec<(String, String)> {
46    let mut ufcs = Vec::new();
47
48    // Conditions 1+2: check each method's type signature
49    for (key, definition) in &module.definitions {
50        let (methods, base_generics_count) = match &definition.body {
51            DefinitionBody::Struct {
52                methods, generics, ..
53            } => (methods, generics.len()),
54            DefinitionBody::Enum {
55                methods, generics, ..
56            } => (methods, generics.len()),
57            DefinitionBody::TypeAlias {
58                methods, generics, ..
59            } => (methods, generics.len()),
60            _ => continue,
61        };
62
63        for (method_name, method_ty) in methods {
64            if is_ufcs_method_type(method_ty, base_generics_count) {
65                ufcs.push((key.to_string(), method_name.to_string()));
66            }
67        }
68    }
69
70    // Condition 3: mixed constrained/unconstrained impl blocks
71    let mut constrained_methods: HashMap<String, Vec<String>> = HashMap::default();
72    let mut unconstrained_types: HashSet<String> = HashSet::default();
73
74    for file in module.files.values() {
75        for item in &file.items {
76            if let Expression::ImplBlock {
77                receiver_name,
78                generics,
79                methods,
80                ..
81            } = item
82            {
83                let qualified_type = Symbol::from_parts(module_id, receiver_name).to_string();
84                if generics.iter().any(|g| !g.bounds.is_empty()) {
85                    let method_names: Vec<String> = methods
86                        .iter()
87                        .filter_map(|m| {
88                            if let Expression::Function { name, .. } = m {
89                                Some(name.to_string())
90                            } else {
91                                None
92                            }
93                        })
94                        .collect();
95                    constrained_methods
96                        .entry(qualified_type)
97                        .or_default()
98                        .extend(method_names);
99                } else {
100                    unconstrained_types.insert(qualified_type);
101                }
102            }
103        }
104    }
105
106    for (type_name, methods) in constrained_methods {
107        if unconstrained_types.contains(&type_name) {
108            for method_name in methods {
109                ufcs.push((type_name.clone(), method_name));
110            }
111        }
112    }
113
114    ufcs
115}