runmat_runtime/builtins/close/
mod.rs1use runmat_builtins::{
7 BuiltinCompletionPolicy, BuiltinDescriptor, BuiltinErrorDescriptor, BuiltinExtensionDescriptor,
8 BuiltinExtensionMode, BuiltinIntegerBackendRule, BuiltinIntegerCapabilityDescriptor,
9 BuiltinIntegerComputationDomain, BuiltinIntegerInputAvailability,
10 BuiltinIntegerInputCapability, BuiltinIntegerOutputClassRule, BuiltinIntegerOverflowRule,
11 BuiltinIntegerOverloadKind, BuiltinIntegerScalarDoubleRule, BuiltinOutputMode,
12 BuiltinParamArity, BuiltinParamDescriptor, BuiltinParamType, BuiltinSignatureDescriptor,
13};
14use runmat_macros::runtime_builtin;
15use runmat_value::Value;
16
17const CLOSE_OUTPUT_RESULT: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
18 name: "result",
19 ty: BuiltinParamType::NumericScalar,
20 arity: BuiltinParamArity::Required,
21 default: None,
22 description: "Scalar double status: 1 when the requested close operation completes, 0 when it is refused.",
23}];
24
25const CLOSE_INPUTS_NONE: [BuiltinParamDescriptor; 0] = [];
26const CLOSE_INPUTS_TARGET: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
27 name: "target",
28 ty: BuiltinParamType::Any,
29 arity: BuiltinParamArity::Required,
30 default: None,
31 description: "Figure target, tcp resource handle, option token, or target container.",
32}];
33const CLOSE_INPUTS_TARGETS: [BuiltinParamDescriptor; 1] = [BuiltinParamDescriptor {
34 name: "targets",
35 ty: BuiltinParamType::Any,
36 arity: BuiltinParamArity::Variadic,
37 default: None,
38 description: "One or more close targets.",
39}];
40
41const CLOSE_SIGNATURES: [BuiltinSignatureDescriptor; 3] = [
42 BuiltinSignatureDescriptor {
43 label: "result = close()",
44 inputs: &CLOSE_INPUTS_NONE,
45 outputs: &CLOSE_OUTPUT_RESULT,
46 },
47 BuiltinSignatureDescriptor {
48 label: "result = close(target)",
49 inputs: &CLOSE_INPUTS_TARGET,
50 outputs: &CLOSE_OUTPUT_RESULT,
51 },
52 BuiltinSignatureDescriptor {
53 label: "result = close(targets...)",
54 inputs: &CLOSE_INPUTS_TARGETS,
55 outputs: &CLOSE_OUTPUT_RESULT,
56 },
57];
58
59const CLOSE_ERROR_INVALID_ARGUMENT: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
60 code: "RM.CLOSE.INVALID_ARGUMENT",
61 identifier: Some("RunMat:close:InvalidArgument"),
62 when: "Close target values are invalid or unsupported.",
63 message: "close: invalid argument",
64};
65const CLOSE_ERROR_INVALID_HANDLE: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
66 code: "RM.CLOSE.INVALID_HANDLE",
67 identifier: Some("RunMat:close:InvalidHandle"),
68 when: "A structure target is not a valid networking resource handle.",
69 message: "close: invalid handle",
70};
71const CLOSE_ERROR_INTERNAL: BuiltinErrorDescriptor = BuiltinErrorDescriptor {
72 code: "RM.CLOSE.INTERNAL",
73 identifier: None,
74 when: "Internal networking, gather, or plotting close processing fails.",
75 message: "close: internal error",
76};
77const CLOSE_ERRORS: [BuiltinErrorDescriptor; 3] = [
78 CLOSE_ERROR_INVALID_ARGUMENT,
79 CLOSE_ERROR_INVALID_HANDLE,
80 CLOSE_ERROR_INTERNAL,
81];
82
83pub const CLOSE_DESCRIPTOR: BuiltinDescriptor = BuiltinDescriptor {
84 signatures: &CLOSE_SIGNATURES,
85 output_mode: BuiltinOutputMode::Fixed,
86 completion_policy: BuiltinCompletionPolicy::Public,
87 errors: &CLOSE_ERRORS,
88};
89
90pub(crate) const CLOSE_INTEGER_FIGURE_NUMBER_EXTENSION: BuiltinExtensionDescriptor =
91 BuiltinExtensionDescriptor {
92 id: "close-integer-figure-number",
93 mode: BuiltinExtensionMode::RunMatOnly,
94 description: "close with a typed integer figure number is a RunMat extension",
95 error_identifier: Some("RunMat:compatibility:CloseIntegerFigureNumberExtension"),
96 };
97
98pub(crate) const CLOSE_VARIADIC_TARGETS_EXTENSION: BuiltinExtensionDescriptor =
99 BuiltinExtensionDescriptor {
100 id: "close-variadic-targets",
101 mode: BuiltinExtensionMode::RunMatOnly,
102 description: "close with separate variadic targets is a RunMat extension",
103 error_identifier: Some("RunMat:compatibility:CloseVariadicTargetsExtension"),
104 };
105
106pub const CLOSE_EXTENSIONS: [BuiltinExtensionDescriptor; 2] = [
107 CLOSE_INTEGER_FIGURE_NUMBER_EXTENSION,
108 CLOSE_VARIADIC_TARGETS_EXTENSION,
109];
110
111const CLOSE_INTEGER_TARGET_INPUTS: [BuiltinIntegerInputCapability; 1] =
112 [BuiltinIntegerInputCapability {
113 name: "fig",
114 classes: &crate::builtins::common::integer_capability::ALL_INTEGER_CLASSES,
115 availability: BuiltinIntegerInputAvailability::RunMatOnly,
116 scalar_double: BuiltinIntegerScalarDoubleRule::Allowed,
117 notes: "RunMat mode interprets a scalar or array of any built-in integer class as figure numbers. The public compatibility contract documents figure numbers but does not advertise typed integer classes.",
118 }];
119
120pub const CLOSE_INTEGER_CAPABILITIES: [BuiltinIntegerCapabilityDescriptor; 1] =
121 [BuiltinIntegerCapabilityDescriptor {
122 form: "status = close(integer_fig)",
123 inputs: &CLOSE_INTEGER_TARGET_INPUTS,
124 computation_domain: BuiltinIntegerComputationDomain::Structural,
125 output_class: BuiltinIntegerOutputClassRule::Double,
126 overflow: BuiltinIntegerOverflowRule::NotApplicable,
127 backend: BuiltinIntegerBackendRule::HostOnly,
128 overload: BuiltinIntegerOverloadKind::StructuralParameter,
129 notes: "Figure numbers are read from authoritative host integer storage and must be positive and representable as RunMat's u32 figure identifier. Resident numeric targets are rejected before networking/provider gather. Successful and no-op plotting closures return scalar double 1; callback-driven refusal remains unavailable until CloseRequestFcn is implemented.",
130 }];
131
132#[runtime_builtin(
133 name = "close",
134 category = "general",
135 summary = "Close figures or networking resources.",
136 keywords = "close,figure,tcpclient,tcpserver,networking",
137 sink = true,
138 suppress_auto_output = true,
139 type_resolver(crate::builtins::io::type_resolvers::close_type),
140 descriptor(crate::builtins::close::CLOSE_DESCRIPTOR),
141 extensions(crate::builtins::close::CLOSE_EXTENSIONS),
142 integer_capabilities(crate::builtins::close::CLOSE_INTEGER_CAPABILITIES),
143 builtin_path = "crate::builtins::close"
144)]
145pub async fn close_builtin(args: Vec<Value>) -> crate::BuiltinResult<f64> {
146 if args.len() > 1 {
147 crate::compatibility::ensure_builtin_extension_enabled(
148 &CLOSE_VARIADIC_TARGETS_EXTENSION,
149 "close",
150 )?;
151 }
152 if args.iter().any(is_typed_integer_value) {
153 crate::compatibility::ensure_builtin_extension_enabled(
154 &CLOSE_INTEGER_FIGURE_NUMBER_EXTENSION,
155 "close",
156 )?;
157 }
158 if let Some(status) = crate::builtins::io::net::close::close_if_network_targets(&args).await? {
159 return Ok(status);
160 }
161
162 close_plotting_targets(&args)
163}
164
165fn is_typed_integer_value(value: &Value) -> bool {
166 matches!(value, Value::Int(_))
167 || matches!(value, Value::Tensor(tensor) if tensor.integer_storage().is_some())
168}
169
170#[cfg(feature = "plot-core")]
171fn close_plotting_targets(args: &[Value]) -> crate::BuiltinResult<f64> {
172 crate::builtins::plotting::close::close_plot_targets(args)
173}
174
175#[cfg(not(feature = "plot-core"))]
176fn close_plotting_targets(_args: &[Value]) -> crate::BuiltinResult<f64> {
177 let mut builder =
178 crate::build_runtime_error(CLOSE_ERROR_INVALID_ARGUMENT.message).with_builtin("close");
179 if let Some(identifier) = CLOSE_ERROR_INVALID_ARGUMENT.identifier {
180 builder = builder.with_identifier(identifier);
181 }
182 Err(builder.build())
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use runmat_value::{IntegerStorage, Tensor};
189
190 #[test]
191 fn compatibility_mode_rejects_all_integer_figure_number_classes() {
192 let _compat = crate::compatibility::push_runmat_extensions_enabled(false);
193 let storages = [
194 IntegerStorage::I8(vec![1]),
195 IntegerStorage::I16(vec![1]),
196 IntegerStorage::I32(vec![1]),
197 IntegerStorage::I64(vec![1]),
198 IntegerStorage::U8(vec![1]),
199 IntegerStorage::U16(vec![1]),
200 IntegerStorage::U32(vec![1]),
201 IntegerStorage::U64(vec![1]),
202 ];
203
204 for storage in storages {
205 let value = Value::Tensor(Tensor::new_integer(storage, vec![1, 1]).expect("figure"));
206 let err = futures::executor::block_on(close_builtin(vec![value]))
207 .expect_err("typed integer extension must be gated");
208 assert_eq!(
209 err.identifier(),
210 Some("RunMat:compatibility:CloseIntegerFigureNumberExtension")
211 );
212 }
213 }
214
215 #[test]
216 fn close_integer_capability_is_structural_and_host_only() {
217 let capability = &CLOSE_INTEGER_CAPABILITIES[0];
218 assert_eq!(capability.inputs[0].classes.len(), 8);
219 assert_eq!(
220 capability.computation_domain,
221 BuiltinIntegerComputationDomain::Structural
222 );
223 assert_eq!(capability.backend, BuiltinIntegerBackendRule::HostOnly);
224 }
225
226 #[test]
227 fn compatibility_mode_rejects_separate_variadic_targets_before_dispatch() {
228 let _compat = crate::compatibility::push_runmat_extensions_enabled(false);
229 let err = futures::executor::block_on(close_builtin(vec![
230 Value::String("clients".into()),
231 Value::String("servers".into()),
232 ]))
233 .expect_err("RunMat-only variadic close targets");
234 assert_eq!(
235 err.identifier(),
236 CLOSE_VARIADIC_TARGETS_EXTENSION.error_identifier
237 );
238 }
239
240 #[test]
241 fn resident_numeric_figure_target_rejects_without_provider_dispatch() {
242 let resident = Value::GpuTensor(runmat_accelerate_api::GpuTensorHandle {
243 shape: vec![1, 1],
244 device_id: u32::MAX,
245 buffer_id: u64::MAX,
246 descriptor: Default::default(),
247 });
248 let err = futures::executor::block_on(close_builtin(vec![resident]))
249 .expect_err("resident figure target");
250 assert!(!err.message().to_ascii_lowercase().contains("provider"));
251 }
252
253 #[test]
254 fn invalid_network_structure_uses_canonical_invalid_handle_error() {
255 let invalid = Value::Struct(runmat_value::StructValue::new());
256 let err = futures::executor::block_on(close_builtin(vec![invalid]))
257 .expect_err("invalid networking handle");
258 assert_eq!(err.identifier(), Some("RunMat:close:InvalidHandle"));
259 assert!(CLOSE_DESCRIPTOR
260 .errors
261 .iter()
262 .any(|error| error.identifier == Some("RunMat:close:InvalidHandle")));
263 }
264}