@@ -216,3 +216,86 @@ fn library_file_name(crate_name: &str) -> String {
216216 format ! ( "lib{crate_name}.so" )
217217 }
218218}
219+
220+ #[ cfg( test) ]
221+ mod tests {
222+ //! The native-plugin loader's *signature → shim binding* mapping. The build +
223+ //! `dlopen` half is OS plumbing (libloading) exercised by the gated e2e test;
224+ //! the bug-prone logic is this type mapping, which was previously 0% covered.
225+ //! Inputs are parsed from tiny interface snippets so we don't hand-build spans.
226+ use super :: * ;
227+ use crate :: syntax:: ast:: Item ;
228+ use crate :: syntax:: parse_source;
229+
230+ fn sig ( src : & str ) -> ( Vec < Param > , Option < TypeRef > ) {
231+ let program = parse_source ( "t.rssi" , src) ;
232+ for item in program. items {
233+ if let Item :: Function ( decl) = item {
234+ return ( decl. params , decl. return_ty ) ;
235+ }
236+ }
237+ panic ! ( "interface snippet declared no function" ) ;
238+ }
239+
240+ #[ test]
241+ fn builds_plain_scalar_binding ( ) {
242+ let ( params, ret) =
243+ sig ( "native fn Adder.add(a: Int, b: Int) -> Int\n effects(native)\n " ) ;
244+ let binding = try_build_binding ( "Adder.add" , "adder::add" , & params, ret. as_ref ( ) )
245+ . expect ( "scalar binding should build" ) ;
246+ assert_eq ! ( binding. symbol, "Adder.add" ) ;
247+ assert_eq ! ( binding. rust_path, "adder::add" ) ;
248+ assert_eq ! ( binding. params, vec![ ShimType :: Int , ShimType :: Int ] ) ;
249+ assert_eq ! ( binding. ret, ShimReturn :: Plain ( ShimType :: Int ) ) ;
250+ assert ! ( binding. mut_indices. is_empty( ) ) ;
251+ }
252+
253+ #[ test]
254+ fn records_mut_param_positions ( ) {
255+ let ( params, ret) =
256+ sig ( "native fn Buf.fill(buffer: mut Bytes, value: Int) -> Unit\n effects(native)\n " ) ;
257+ let binding = try_build_binding ( "Buf.fill" , "buf::fill" , & params, ret. as_ref ( ) )
258+ . expect ( "mut binding should build" ) ;
259+ assert_eq ! ( binding. mut_indices, vec![ 0 ] ) ;
260+ assert_eq ! ( binding. params, vec![ ShimType :: Bytes , ShimType :: Int ] ) ;
261+ }
262+
263+ #[ test]
264+ fn maps_result_and_nested_container_types ( ) {
265+ let ( params, ret) = sig (
266+ "native fn P.run(items: read List<String>) -> Result<Option<Int>, String>\n effects(native)\n " ,
267+ ) ;
268+ let binding = try_build_binding ( "P.run" , "p::run" , & params, ret. as_ref ( ) )
269+ . expect ( "container binding should build" ) ;
270+ assert_eq ! (
271+ binding. params,
272+ vec![ ShimType :: List ( Box :: new( ShimType :: String ) ) ]
273+ ) ;
274+ assert_eq ! (
275+ binding. ret,
276+ ShimReturn :: Result ( ShimType :: Option ( Box :: new( ShimType :: Int ) ) )
277+ ) ;
278+ }
279+
280+ #[ test]
281+ fn missing_return_type_is_plain_unit ( ) {
282+ let ( params, ret) = sig ( "native fn X.g(a: Int)\n effects(native)\n " ) ;
283+ let binding = try_build_binding ( "X.g" , "x::g" , & params, ret. as_ref ( ) )
284+ . expect ( "unit-return binding should build" ) ;
285+ assert_eq ! ( binding. ret, ShimReturn :: Plain ( ShimType :: Unit ) ) ;
286+ }
287+
288+ #[ test]
289+ fn rejects_unsupported_param_type ( ) {
290+ // A user type the value bridge can't represent → the binding is skipped
291+ // (the VM reports "no host binding" only if the program actually calls it).
292+ let ( params, ret) = sig ( "native fn X.f(cfg: read Config) -> Unit\n effects(native)\n " ) ;
293+ assert ! ( try_build_binding( "X.f" , "x::f" , & params, ret. as_ref( ) ) . is_none( ) ) ;
294+ }
295+
296+ #[ test]
297+ fn rejects_unsupported_return_type ( ) {
298+ let ( params, ret) = sig ( "native fn X.h(a: Int) -> Config\n effects(native)\n " ) ;
299+ assert ! ( try_build_binding( "X.h" , "x::h" , & params, ret. as_ref( ) ) . is_none( ) ) ;
300+ }
301+ }
0 commit comments