Skip to main content

lisette_semantics/
call_target.rs

1use ecow::EcoString;
2
3use syntax::ast::Expression;
4use syntax::types::Type;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct CallTarget {
8    pub module: EcoString,
9    pub recv_type: Option<EcoString>,
10    pub fn_name: EcoString,
11}
12
13impl CallTarget {
14    pub fn is(&self, module: &str, fn_name: &str) -> bool {
15        self.recv_type.is_none() && self.module == module && self.fn_name == fn_name
16    }
17
18    pub fn is_method(&self, module: &str, recv_type: &str, fn_name: &str) -> bool {
19        self.recv_type.as_ref().is_some_and(|r| r == recv_type)
20            && self.module == module
21            && self.fn_name == fn_name
22    }
23}
24
25/// Resolves calls of the form `pkg.Function(...)` or `recv.Method(...)` to
26/// their post-inference identity. Alias-resilient: an aliased import like
27/// `import s "go:strings"` still resolves to `module = "go:strings"`. Bare
28/// identifier callees and parameter-typed receivers return `None`.
29pub fn resolve_call(expr: &Expression) -> Option<CallTarget> {
30    let Expression::Call {
31        expression: callee, ..
32    } = expr
33    else {
34        return None;
35    };
36    let Expression::DotAccess {
37        expression: base,
38        member,
39        ..
40    } = callee.unwrap_parens()
41    else {
42        return None;
43    };
44    let base_ty = base.get_type().strip_refs();
45    match base_ty {
46        Type::ImportNamespace(module_id) => Some(CallTarget {
47            module: module_id,
48            recv_type: None,
49            fn_name: member.clone(),
50        }),
51        Type::Nominal { id, .. } => {
52            let module = id.without_last_segment()?;
53            Some(CallTarget {
54                module: EcoString::from(module),
55                recv_type: Some(EcoString::from(id.last_segment())),
56                fn_name: member.clone(),
57            })
58        }
59        _ => None,
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn is_matches_free_function() {
69        let t = CallTarget {
70            module: EcoString::from("go:strings"),
71            recv_type: None,
72            fn_name: EcoString::from("Contains"),
73        };
74        assert!(t.is("go:strings", "Contains"));
75        assert!(!t.is("go:strings", "Other"));
76        assert!(!t.is("go:other", "Contains"));
77        assert!(!t.is_method("go:strings", "String", "Contains"));
78    }
79
80    #[test]
81    fn is_method_matches_nominal_receiver() {
82        let t = CallTarget {
83            module: EcoString::from("go:time"),
84            recv_type: Some(EcoString::from("Time")),
85            fn_name: EcoString::from("Equal"),
86        };
87        assert!(t.is_method("go:time", "Time", "Equal"));
88        assert!(!t.is_method("go:time", "Duration", "Equal"));
89        assert!(!t.is("go:time", "Equal"));
90    }
91}