Skip to main content

rl_lang/checker/scope/
call.rs

1//! Function call type checking - path resolution and argument validation.
2
3use crate::{
4    ast::statements::TypeAnnotation,
5    checker::structs::{CheckType, TypeChecker},
6    interpreter::stdlib,
7    utils::{span::Span, suggest::closest_match},
8};
9
10impl TypeChecker {
11    /// Resolves a call path and checks argument types.
12    ///
13    /// Resolution order:
14    /// 1. Full stdlib path (`std::io::print`) via [`root_module`]
15    /// 2. Single-name stdlib shorthand (`print`) via [`stdlib_fn_names`]
16    /// 3. User-defined name via [`lookup`]
17    /// 4. Error - unknown function, with a "did you mean?" suggestion from stdlib keywords
18    ///
19    /// Stdlib calls always return [`CheckType::Unknown`] since their return
20    /// types are not tracked statically.
21    pub fn check_call_path(
22        &mut self,
23        path: &[String],
24        arg_types: &[(CheckType, Span)],
25        span: Span,
26    ) -> CheckType {
27        // stdlib path (std::io::print)
28        if self.root_module.resolve(path).is_some() {
29            self.push_stdlib_hover(path, span);
30            return CheckType::Unknown;
31        }
32
33        if path.len() == 1 {
34            let name = &path[0];
35
36            if self.stdlib_fn_names.contains(name.as_str()) {
37                self.push_stdlib_hover(path, span);
38                return CheckType::Unknown;
39            }
40            let item_type = self.lookup(name, span);
41            return self.check_call_value(item_type, arg_types, span);
42        }
43
44        let suggestion = if let Some(last) = path.last() {
45            let candidates = stdlib::math::KEYWORDS
46                .iter()
47                .chain(stdlib::math::constants::KEYWORDS)
48                .chain(stdlib::io::KEYWORDS)
49                .chain(stdlib::string::KEYWORDS)
50                .chain(stdlib::types::KEYWORDS)
51                .chain(stdlib::array::KEYWORDS)
52                .chain(stdlib::path::KEYWORDS)
53                .chain(stdlib::fs::KEYWORDS)
54                .copied();
55            closest_match(last, candidates)
56        } else {
57            None
58        };
59
60        self.error_with_help(
61            format!("undefined function {}", path.join("::")),
62            span,
63            suggestion,
64        );
65
66        CheckType::Unknown
67    }
68
69    /// Checks that a resolved callee value is callable and receives the correct arguments.
70    ///
71    /// - `Unknown` and `Known(Fn)` pass through without argument checking
72    /// - `Function { params, return_type }` validates arity and argument types,
73    ///   then returns `Known(return_type)`
74    /// - Anything else emits a "not callable" error
75    pub fn check_call_value(
76        &mut self,
77        callee_type: CheckType,
78        arg_types: &[(CheckType, Span)],
79        span: Span,
80    ) -> CheckType {
81        match callee_type {
82            CheckType::Unknown => CheckType::Unknown,
83            CheckType::Known(TypeAnnotation::Fn) => CheckType::Unknown,
84            CheckType::Function {
85                params,
86                return_type,
87            } => {
88                if params.len() != arg_types.len() {
89                    self.error(
90                        format!(
91                            "function expects {} argument(s), got {}",
92                            params.len(),
93                            arg_types.len()
94                        ),
95                        span,
96                    );
97                    return CheckType::Known(return_type);
98                }
99                for (expected_type, (actual_type, arg_span)) in params.iter().zip(arg_types.iter())
100                {
101                    let expected = CheckType::Known(expected_type.clone());
102
103                    if !actual_type.matches(&expected) {
104                        self.error(
105                            format!(
106                                "type mismatch: expected {}, got {}",
107                                expected.info(),
108                                actual_type.info()
109                            ),
110                            *arg_span,
111                        );
112                    }
113                }
114                CheckType::Known(return_type)
115            }
116
117            other => {
118                self.error(format!("{} is not callable", other.info()), span);
119                CheckType::Unknown
120            }
121        }
122    }
123}