runmat_runtime/builtins/introspection/
getmethod.rs1use runmat_builtins::{
2 BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinOutputMode,
3 BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType, BuiltinSignatureDescriptor, Value,
4};
5use runmat_macros::runtime_builtin;
6
7const GETMETHOD_OUTPUT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
8 name: "fh",
9 ty: BuiltinParamType::Any,
10 arity: BuiltinParamArity::Required,
11 default: None,
12 description: "Bound method closure/handle.",
13}];
14
15const GETMETHOD_INPUTS: [BuiltinParamDescriptor; 2] = [
16 BuiltinParamDescriptor {
17 name: "obj_or_class",
18 ty: BuiltinParamType::Any,
19 arity: BuiltinParamArity::Required,
20 default: None,
21 description: "Object receiver or class reference.",
22 },
23 BuiltinParamDescriptor {
24 name: "name",
25 ty: BuiltinParamType::StringScalar,
26 arity: BuiltinParamArity::Required,
27 default: None,
28 description: "Method name.",
29 },
30];
31
32const GETMETHOD_SIGNATURES: [BuiltinSignatureDescriptor; 1] = [BuiltinSignatureDescriptor {
33 label: "fh = getmethod(obj_or_class, name)",
34 inputs: &GETMETHOD_INPUTS,
35 outputs: &GETMETHOD_OUTPUT,
36}];
37
38const GETMETHOD_ERROR_NAME_INVALID: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
39 code: "RM.GETMETHOD.NAME_INVALID",
40 identifier: Some("RunMat:GetMethodNameInvalid"),
41 when: "Method name is empty.",
42 message: "getmethod: method name must not be empty",
43};
44
45const GETMETHOD_ERROR_RECEIVER_UNSUPPORTED: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
46 code: "RM.GETMETHOD.RECEIVER_UNSUPPORTED",
47 identifier: Some("RunMat:GetMethodReceiverUnsupported"),
48 when: "Receiver is neither object nor class reference.",
49 message: "getmethod: unsupported receiver",
50};
51
52const GETMETHOD_ERROR_METHOD_PRIVATE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
53 code: "RM.GETMETHOD.METHOD_PRIVATE",
54 identifier: Some("RunMat:MethodPrivate"),
55 when: "Resolved method exists but is inaccessible from the current class scope.",
56 message: "getmethod: method is not accessible from current scope",
57};
58
59const GETMETHOD_ERRORS: [BuiltinErrorDescriptor; 3] = [
60 GETMETHOD_ERROR_NAME_INVALID,
61 GETMETHOD_ERROR_RECEIVER_UNSUPPORTED,
62 GETMETHOD_ERROR_METHOD_PRIVATE,
63];
64
65pub const GETMETHOD_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
66 signatures: &GETMETHOD_SIGNATURES,
67 output_mode: BuiltinOutputMode::Fixed,
68 completion_policy: BuiltinCompletionPolicy::Public,
69 errors: &GETMETHOD_ERRORS,
70};
71
72pub(crate) fn dispatch_getmethod(obj: Value, name: String) -> crate::BuiltinResult<Value> {
73 fn ensure_method_accessible(class_name: &str, method_name: &str) -> crate::BuiltinResult<()> {
74 let Some((method, owner)) = runmat_builtins::lookup_method(class_name, method_name) else {
75 return Ok(());
76 };
77 let caller_class = crate::class_access_context();
78 let access_allowed = match method.access {
79 runmat_builtins::Access::Public => true,
80 runmat_builtins::Access::Private => caller_class.as_deref() == Some(owner.as_str()),
81 runmat_builtins::Access::Protected => caller_class
82 .as_deref()
83 .is_some_and(|caller| runmat_builtins::is_class_or_subclass(caller, &owner)),
84 };
85 if access_allowed {
86 return Ok(());
87 }
88 Err(crate::runtime_descriptor_error_with_detail(
89 "getmethod",
90 &GETMETHOD_ERROR_METHOD_PRIVATE,
91 format!("{}.{}", class_name, method_name),
92 ))
93 }
94
95 let method_name = name.trim();
96 if method_name.is_empty() {
97 return Err(crate::runtime_descriptor_error(
98 "getmethod",
99 &GETMETHOD_ERROR_NAME_INVALID,
100 ));
101 }
102 let caller_scope = crate::class_access_context()
103 .map(Value::String)
104 .unwrap_or_else(|| Value::String(String::new()));
105 match obj {
106 Value::Object(o) => {
107 ensure_method_accessible(&o.class_name, method_name)?;
108 if let Some((resolved, _owner)) =
109 runmat_builtins::lookup_method(&o.class_name, method_name)
110 {
111 return Ok(Value::Closure(runmat_builtins::Closure {
112 function_name: resolved.function_name.clone(),
113 bound_function: crate::user_functions::resolve_semantic_function_by_name(
114 &resolved.function_name,
115 ),
116 captures: vec![Value::Object(o)],
117 }));
118 }
119 Ok(Value::Closure(runmat_builtins::Closure {
120 function_name: crate::CALL_BOUND_METHOD_BUILTIN_NAME.to_string(),
121 bound_function: None,
122 captures: vec![
123 Value::Object(o),
124 Value::String(method_name.to_string()),
125 caller_scope.clone(),
126 ],
127 }))
128 }
129 Value::HandleObject(h) => {
130 ensure_method_accessible(&h.class_name, method_name)?;
131 if let Some((resolved, _owner)) =
132 runmat_builtins::lookup_method(&h.class_name, method_name)
133 {
134 return Ok(Value::Closure(runmat_builtins::Closure {
135 function_name: resolved.function_name.clone(),
136 bound_function: crate::user_functions::resolve_semantic_function_by_name(
137 &resolved.function_name,
138 ),
139 captures: vec![Value::HandleObject(h)],
140 }));
141 }
142 Ok(Value::Closure(runmat_builtins::Closure {
143 function_name: crate::CALL_BOUND_METHOD_BUILTIN_NAME.to_string(),
144 bound_function: None,
145 captures: vec![
146 Value::HandleObject(h),
147 Value::String(method_name.to_string()),
148 caller_scope,
149 ],
150 }))
151 }
152 Value::ClassRef(cls) => {
153 ensure_method_accessible(&cls, method_name)?;
154 crate::builtins::introspection::function_handle_text::dispatch_str2func(Value::String(
155 format!("@{cls}.{method_name}"),
156 ))
157 }
158 other => Err(crate::runtime_descriptor_error_with_detail(
159 "getmethod",
160 &GETMETHOD_ERROR_RECEIVER_UNSUPPORTED,
161 format!("{other:?}"),
162 )),
163 }
164}
165
166#[runtime_builtin(
167 name = "getmethod",
168 category = "introspection",
169 summary = "Create a method-bound function handle from object/class and method name.",
170 keywords = "method,function_handle,classdef,dispatch",
171 descriptor(crate::builtins::introspection::getmethod::GETMETHOD_DESCRIPTOR),
172 builtin_path = "crate::builtins::introspection::getmethod"
173)]
174pub async fn getmethod_builtin(obj: Value, name: String) -> crate::BuiltinResult<Value> {
175 dispatch_getmethod(obj, name)
176}