Skip to main content

runmat_runtime/call/
identity.rs

1use runmat_types::{CallableIdentity, QualifiedName, SymbolName};
2
3/// Builds the stable identity used for an externally resolved member.
4pub fn external_qualified_identity(base: &str, member: &str) -> CallableIdentity {
5    let base_segments = {
6        let split = base.split('.').collect::<Vec<_>>();
7        if !split.is_empty() && split.iter().all(|segment| !segment.is_empty()) {
8            split
9                .into_iter()
10                .map(|segment| SymbolName(segment.to_string()))
11                .collect::<Vec<_>>()
12        } else {
13            vec![SymbolName(base.to_string())]
14        }
15    };
16    let mut segments = base_segments;
17    segments.push(SymbolName(member.to_string()));
18    CallableIdentity::ExternalName(QualifiedName(segments))
19}
20
21/// Returns a lossless source-facing name only when the identity carries one.
22pub fn strict_callable_display_name(identity: &CallableIdentity) -> Option<String> {
23    match identity {
24        CallableIdentity::BoundFunction(_) | CallableIdentity::AnonymousFunction(_) => None,
25        CallableIdentity::ExternalFunction { display_name, .. } => {
26            (!display_name.is_empty()).then_some(display_name.clone())
27        }
28        CallableIdentity::Builtin(id) => (!id.0.is_empty()).then_some(id.0.clone()),
29        CallableIdentity::Imported(path) => path.module.display_name(),
30        CallableIdentity::Method(id) => (!id.0.is_empty()).then_some(id.0.clone()),
31        CallableIdentity::DynamicName(name) => (!name.0.is_empty()).then_some(name.0.clone()),
32        CallableIdentity::ExternalName(QualifiedName(segments)) => {
33            if segments.is_empty() || segments.iter().any(|segment| segment.0.is_empty()) {
34                return None;
35            }
36            Some(
37                segments
38                    .iter()
39                    .map(|segment| segment.0.as_str())
40                    .collect::<Vec<_>>()
41                    .join("."),
42            )
43        }
44    }
45}
46
47pub fn external_qualified_display_name(base: &str, member: &str) -> String {
48    strict_callable_display_name(&external_qualified_identity(base, member))
49        .expect("external qualified identity should always have a display name")
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn qualified_identity_preserves_valid_segments() {
58        let CallableIdentity::ExternalName(QualifiedName(segments)) =
59            external_qualified_identity("pkg.Point", "origin")
60        else {
61            panic!("expected external name");
62        };
63        assert_eq!(
64            segments,
65            vec![
66                SymbolName("pkg".into()),
67                SymbolName("Point".into()),
68                SymbolName("origin".into())
69            ]
70        );
71    }
72
73    #[test]
74    fn malformed_base_remains_one_lossless_segment() {
75        let CallableIdentity::ExternalName(QualifiedName(segments)) =
76            external_qualified_identity("pkg..Point", "origin")
77        else {
78            panic!("expected external name");
79        };
80        assert_eq!(
81            segments,
82            vec![SymbolName("pkg..Point".into()), SymbolName("origin".into())]
83        );
84    }
85}