diff --git a/examples/command-line-args.roc b/examples/command-line-args.roc index 62f3fac6..34701e75 100644 --- a/examples/command-line-args.roc +++ b/examples/command-line-args.roc @@ -2,13 +2,16 @@ app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.23.0-rc1/3hT3SoHZ6qbEsa9qVFLUW3547U5LeoNd1KbpqLpz4r1i.tar.zst" } import pf.OsStr +import pf.Env import pf.Stdout import pf.Stderr main! : List(OsStr) => Try({}, _) main! = |args| { - # Skip first arg (executable path), get the remaining args - match args.drop_first(1) { + program_name = Env.program_name!()? + Stdout.line!("program name: ${OsStr.display(program_name)}")? + + match args { [first_arg, ..] => { Stdout.line!("received argument: ${OsStr.display(first_arg)}")? diff --git a/examples/file-accessed-modified-created-time.roc b/examples/file-accessed-modified-created-time.roc index 62765163..56bdd8bf 100644 --- a/examples/file-accessed-modified-created-time.roc +++ b/examples/file-accessed-modified-created-time.roc @@ -36,7 +36,7 @@ main! = |args| { ## Parse the first argument into a Path path_argument : List(OsStr) -> Try(Path, _) path_argument = |args| - match args.drop_first(1) { + match args { [first, ..] => Ok(Path.from_os_str(first)) [] => Err(MissingPathArgument) } diff --git a/examples/file-permissions.roc b/examples/file-permissions.roc index f4d40478..ef198d15 100644 --- a/examples/file-permissions.roc +++ b/examples/file-permissions.roc @@ -29,7 +29,7 @@ main! = |args| { ## Parse the first argument into a Path path_argument : List(OsStr) -> Try(Path, _) path_argument = |args| - match args.drop_first(1) { + match args { [first, ..] => Ok(Path.from_os_str(first)) [] => Err(MissingPathArgument) } diff --git a/examples/file-size.roc b/examples/file-size.roc index 634d361c..c03e0adf 100644 --- a/examples/file-size.roc +++ b/examples/file-size.roc @@ -21,7 +21,7 @@ main! = |args| { path_argument : List(OsStr) -> Try(Path, [MissingPathArgument, ..]) path_argument = |args| - match args.drop_first(1) { + match args { [first, ..] => Ok(Path.from_os_str(first)) [] => Err(MissingPathArgument) } diff --git a/examples/filesystem-tools.roc b/examples/filesystem-tools.roc index 4933410f..dcbc80db 100644 --- a/examples/filesystem-tools.roc +++ b/examples/filesystem-tools.roc @@ -9,7 +9,7 @@ import pf.Stdout main! : List(OsStr) => Try({}, _) main! = |args| { - (source_arg, destination_arg) = match args.drop_first(1) { + (source_arg, destination_arg) = match args { [source, destination] => (source, destination) _ => return Err(MissingPaths) } diff --git a/examples/hello.roc b/examples/hello.roc index 8ea7bdcc..c128dfa9 100644 --- a/examples/hello.roc +++ b/examples/hello.roc @@ -17,7 +17,7 @@ main! = |args| { greeting_name : List(OsStr) -> Str greeting_name = |args| - match args.drop_first(1) { + match args { [first, ..] => OsStr.display(first) [] => "friend" } diff --git a/examples/path.roc b/examples/path.roc index 0fd43306..687509c9 100644 --- a/examples/path.roc +++ b/examples/path.roc @@ -24,7 +24,7 @@ main! = |args| { } path_argument = |args| - match args.drop_first(1) { + match args { [first, ..] => Ok(Path.from_os_str(first)) [] => Err(MissingPathArgument) } diff --git a/examples/process-control.roc b/examples/process-control.roc index 7a3fee66..35ba7434 100644 --- a/examples/process-control.roc +++ b/examples/process-control.roc @@ -9,7 +9,7 @@ import pf.Stdout main! : List(OsStr) => Try({}, _) main! = |args| { - (program, arguments) = match args.drop_first(1) { + (program, arguments) = match args { [command, .. as rest] => (command, rest) [] => return Err(MissingCommand) } diff --git a/platform/Env.roc b/platform/Env.roc index a365faa7..33efca6a 100644 --- a/platform/Env.roc +++ b/platform/Env.roc @@ -84,6 +84,19 @@ Env :: [].{ Err(ExePathUnavailable) => Err(ExePathUnavailable) } + ## Gets the program name supplied by the process launcher as its first argument. + ## + ## This is conventionally the executable name or path, but launchers may supply + ## another value. Unlike [`exe_path!`](#exe_path!), it is returned as an + ## [`OsStr`](OsStr), preserving the value exactly without treating it as a path. + ## Returns `Err(ProgramNameUnavailable)` if the launcher supplied no first argument. + program_name! : () => Try(OsStr, [ProgramNameUnavailable, ..]) + program_name! = || + match Host.env_program_name!() { + Ok(raw) => Ok(OsStr.from_raw(raw)) + Err(ProgramNameUnavailable) => Err(ProgramNameUnavailable) + } + ## Atomically create a private directory in the system temporary directory. ## The caller owns cleanup. Unix directories have mode 0700. create_temp_dir! : () => Try(Path.Path, [TempDirErr(IOErr), ..]) diff --git a/platform/Host.roc b/platform/Host.roc index 8ce9dd93..53122f27 100644 --- a/platform/Host.roc +++ b/platform/Host.roc @@ -157,4 +157,6 @@ Host :: [].{ tcp_local_port! : TcpListener => Try(U16, Str) tcp_accept! : TcpListener, U64 => Try(TcpStream, Str) tcp_listener_close! : TcpListener => Try({}, Str) + + env_program_name! : () => Try(NativeOsStr, [ProgramNameUnavailable]) } diff --git a/platform/main.roc b/platform/main.roc index f7053a35..58da87a0 100644 --- a/platform/main.roc +++ b/platform/main.roc @@ -2,6 +2,8 @@ ## SQLite, environment, random, and UTC effects. platform "" requires { + ## The application entry point receives command-line arguments only; the + ## launcher-supplied program name is available from Env.program_name!. main! : List([Utf8(Str), UnixBytes(List(U8)), WindowsU16s(List(U16))]) => Try({}, [Exit(I32), ..]) } exposes [Cmd, Env, File, Http, IOErr, Locale, Monotonic, OsStr, Path, Random, Sleep, Sqlite, Stdin, Stdout, Stderr, Tcp, Tty, Url, Utc] @@ -100,6 +102,7 @@ platform "" "hosted_tcp_local_port": Host.tcp_local_port!, "hosted_tcp_accept": Host.tcp_accept!, "hosted_tcp_listener_close": Host.tcp_listener_close!, + "hosted_env_program_name": Host.env_program_name!, } targets: { inputs_dir: "targets/", @@ -132,6 +135,8 @@ import Tty import Url import Utc +## The native host removes the invocation name (argv[0]) before calling this +## function. Applications can read that value separately with Env.program_name!. main_for_host! : List(OsStr.OsStr) => I32 main_for_host! = |args| match main!(args) { diff --git a/scripts/test_spec.json b/scripts/test_spec.json index b79c1220..84de8410 100644 --- a/scripts/test_spec.json +++ b/scripts/test_spec.json @@ -39,11 +39,13 @@ "name": "one-argument", "args": ["foo"], "contains": [ + "program name:", "received argument: foo", "back to OsStr: OsStr.", "argument 1 is valid UTF-8: foo" ], "regex": [ + "program name: .*command-line-args(?:\\.exe)?", "(?:Unix argument, bytes|Windows argument, UTF-16 code units): \\[102, 111, 111\\]" ] }, diff --git a/src/lib.rs b/src/lib.rs index 74a827ca..c76be0c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ use std::ffi::{c_char, c_void, OsStr as StdOsStr, OsString}; use std::fs; use std::io::{self, BufRead, BufReader, Read, Write}; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Mutex; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; @@ -108,6 +109,7 @@ extern "C" { static DEBUG_OR_EXPECT_CALLED: AtomicBool = AtomicBool::new(false); static mut ROC_HOST: *mut RocHost = core::ptr::null_mut(); +static PROGRAM_NAME: Mutex> = Mutex::new(None); fn set_roc_host(roc_host: *mut RocHost) { unsafe { @@ -125,6 +127,19 @@ fn roc_host_ptr() -> *mut RocHost { } } +fn set_program_name(value: Option) { + *PROGRAM_NAME + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) = value; +} + +fn program_name() -> Option { + PROGRAM_NAME + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .clone() +} + pub(crate) fn roc_host() -> &'static RocHost { unsafe { &*roc_host_ptr() } } @@ -345,6 +360,22 @@ fn try_env_exe_path_err() -> HostEnvExePathResult { } } +fn try_env_program_name_ok(value: NativeOsStr) -> HostEnvProgramNameResult { + HostEnvProgramNameResult { + payload: HostEnvProgramNameResultPayload { + ok: ManuallyDrop::new(value), + }, + tag: HostEnvProgramNameResultTag::Ok, + } +} + +fn try_env_program_name_err() -> HostEnvProgramNameResult { + HostEnvProgramNameResult { + payload: HostEnvProgramNameResultPayload { err: [] }, + tag: HostEnvProgramNameResultTag::Err, + } +} + fn try_env_set_cwd_ok() -> HostEnvSetCwdResult { HostEnvSetCwdResult { payload: HostEnvSetCwdResultPayload { ok: [] }, @@ -1258,6 +1289,15 @@ pub extern "C" fn hosted_env_exe_path() -> HostEnvExePathResult { } } +#[no_mangle] +pub extern "C" fn hosted_env_program_name() -> HostEnvProgramNameResult { + let roc_host = roc_host(); + match program_name() { + Some(value) => try_env_program_name_ok(native_os_str_from_os_str(&value, roc_host)), + None => try_env_program_name_err(), + } +} + #[no_mangle] pub extern "C" fn hosted_env_temp_dir() -> UnixBytesOrUtf8OrWindowsU16s { let roc_host = roc_host(); @@ -2098,55 +2138,83 @@ pub extern "C" fn roc_crashed(bytes: *const u8, len: usize) { } #[cfg(unix)] -fn build_args_list(argc: i32, argv: *const *const c_char, roc_host: &RocHost) -> RocList { +fn collect_process_args(argc: i32, argv: *const *const c_char) -> Vec { if argc <= 0 || argv.is_null() { - return RocList::empty(); + return Vec::new(); } - let list = unsafe { RocList::::allocate(argc as usize, roc_host) }; - for index in 0..argc as isize { - unsafe { - let arg_ptr = *argv.offset(index); - if arg_ptr.is_null() { - break; - } - let arg = CStr::from_ptr(arg_ptr).to_bytes(); - list.elements.offset(index).write(OsStr { - payload: OsStrPayload { - unix_bytes: ManuallyDrop::new(roc_u8_list_from_slice(arg, roc_host)), - }, - tag: OsStrTag::UnixBytes, - }); - } - } - list + use std::os::unix::ffi::OsStringExt; + + let raw_args = unsafe { core::slice::from_raw_parts(argv, argc as usize) }; + raw_args + .iter() + .take_while(|arg| !arg.is_null()) + .map(|arg| unsafe { OsString::from_vec(CStr::from_ptr(*arg).to_bytes().to_vec()) }) + .collect() } #[cfg(windows)] -fn build_args_list(_argc: i32, _argv: *const *const c_char, roc_host: &RocHost) -> RocList { - use std::os::windows::ffi::OsStrExt; +fn collect_process_args(_argc: i32, _argv: *const *const c_char) -> Vec { + std::env::args_os().collect() +} + +#[cfg(not(any(unix, windows)))] +fn collect_process_args(_argc: i32, _argv: *const *const c_char) -> Vec { + Vec::new() +} + +fn split_program_name(mut process_args: Vec) -> (Option, Vec) { + if process_args.is_empty() { + (None, process_args) + } else { + let args = process_args.split_off(1); + (process_args.pop(), args) + } +} - let args = std::env::args_os().collect::>(); +fn build_args_list(args: &[OsString], roc_host: &RocHost) -> RocList { let list = unsafe { RocList::::allocate(args.len(), roc_host) }; for (index, arg) in args.iter().enumerate() { - let units = arg.encode_wide().collect::>(); + #[cfg(unix)] + use std::os::unix::ffi::OsStrExt; + #[cfg(windows)] + use std::os::windows::ffi::OsStrExt; + unsafe { + #[cfg(unix)] + list.elements.add(index).write(OsStr { + payload: OsStrPayload { + unix_bytes: ManuallyDrop::new(roc_u8_list_from_slice( + arg.as_os_str().as_bytes(), + roc_host, + )), + }, + tag: OsStrTag::UnixBytes, + }); + + #[cfg(windows)] + { + let units = arg.encode_wide().collect::>(); + list.elements.add(index).write(OsStr { + payload: OsStrPayload { + windows_u16s: ManuallyDrop::new(roc_u16_list_from_slice(&units, roc_host)), + }, + tag: OsStrTag::WindowsU16s, + }); + } + + #[cfg(not(any(unix, windows)))] list.elements.add(index).write(OsStr { payload: OsStrPayload { - windows_u16s: ManuallyDrop::new(roc_u16_list_from_slice(&units, roc_host)), + utf8: ManuallyDrop::new(RocStr::empty()), }, - tag: OsStrTag::WindowsU16s, + tag: OsStrTag::Utf8, }); } } list } -#[cfg(not(any(unix, windows)))] -fn build_args_list(_argc: i32, _argv: *const *const c_char, _roc_host: &RocHost) -> RocList { - RocList::empty() -} - #[cfg(not(test))] #[no_mangle] pub extern "C" fn main(argc: i32, argv: *const *const c_char) -> i32 { @@ -2162,7 +2230,10 @@ pub fn rust_main(argc: i32, argv: *const *const c_char) -> i32 { let mut roc_host = resources::make_host(); set_roc_host(&mut roc_host); - let args_list = build_args_list(argc, argv, &roc_host); + let (program_name, args) = split_program_name(collect_process_args(argc, argv)); + set_program_name(program_name); + + let args_list = build_args_list(&args, &roc_host); let mut exit_code = unsafe { roc_main(args_list) }; process_service::shutdown(); @@ -2171,6 +2242,7 @@ pub fn rust_main(argc: i32, argv: *const *const c_char) -> i32 { exit_code = 1; } + set_program_name(None); set_roc_host(core::ptr::null_mut()); exit_code } @@ -2228,6 +2300,50 @@ mod tests { values.iter().map(|value| (*value).to_string()).collect() } + #[test] + fn separates_program_name_from_command_line_arguments() { + let (program_name, args) = split_program_name(vec![ + OsString::from("roc-app"), + OsString::from("first"), + OsString::from("second"), + ]); + + assert_eq!(program_name, Some(OsString::from("roc-app"))); + assert_eq!(args, [OsString::from("first"), OsString::from("second")]); + } + + #[test] + fn reports_an_unavailable_program_name_for_an_empty_process_argument_list() { + let (program_name, args) = split_program_name(Vec::new()); + + assert_eq!(program_name, None); + assert!(args.is_empty()); + } + + #[cfg(unix)] + #[test] + fn collects_process_arguments_without_losing_non_utf8_bytes() { + use std::ffi::CString; + use std::os::unix::ffi::OsStringExt; + + let values = [ + CString::new(b"launcher-name".as_slice()).unwrap(), + CString::new(vec![b'a', 0xff, b'b']).unwrap(), + ]; + let pointers = values + .iter() + .map(|value| value.as_ptr()) + .collect::>(); + + assert_eq!( + collect_process_args(pointers.len() as i32, pointers.as_ptr()), + [ + OsString::from("launcher-name"), + OsString::from_vec(vec![b'a', 0xff, b'b']), + ] + ); + } + #[test] fn normalizes_posix_locale_forms() { assert_eq!(normalize_locale("en_US"), Some("en-US".to_string())); diff --git a/src/roc_platform_abi.rs b/src/roc_platform_abi.rs index 63f515a4..3cd13418 100644 --- a/src/roc_platform_abi.rs +++ b/src/roc_platform_abi.rs @@ -7693,6 +7693,97 @@ const _: () = assert!(core::mem::align_of::() == 4, "Hos #[cfg(target_pointer_width = "32")] const _: () = assert!(core::mem::offset_of!(HostTcpLocalPortResult, tag) == 12, "HostTcpLocalPortResult tag offset mismatch"); +/// Tag discriminant for Try. +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HostEnvProgramNameResultTag { + Err = 0, + Ok = 1, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub union HostEnvProgramNameResultPayload { + pub err: [u8; 0], + pub ok: core::mem::ManuallyDrop, +} + +#[cfg(target_pointer_width = "32")] +#[repr(align(4))] +#[derive(Clone, Copy)] +pub struct HostEnvProgramNameResultPayloadAlignment; + +/// Tag union: Try +#[cfg(target_pointer_width = "32")] +#[repr(C)] +#[derive(Clone, Copy)] +pub struct HostEnvProgramNameResult { + pub _payload_alignment: [HostEnvProgramNameResultPayloadAlignment; 0], + pub payload: [u8; 16], + pub tag: HostEnvProgramNameResultTag, +} + +/// Tag union: Try +#[cfg(not(target_pointer_width = "32"))] +#[repr(C)] +#[derive(Clone, Copy)] +pub struct HostEnvProgramNameResult { + pub payload: HostEnvProgramNameResultPayload, + pub tag: HostEnvProgramNameResultTag, +} + +impl HostEnvProgramNameResult { + /// Borrow the `Ok` payload without creating another owner. + /// + /// # Safety + /// `self.tag` must be `HostEnvProgramNameResultTag::Ok` and the payload must still be initialized. + #[cfg(target_pointer_width = "32")] + pub unsafe fn borrow_payload_ok_unchecked(&self) -> &UnixBytesOrUtf8OrWindowsU16s { + unsafe { &*(self.payload.as_ptr() as *const UnixBytesOrUtf8OrWindowsU16s) } + } + + /// Borrow the `Ok` payload without creating another owner. + /// + /// # Safety + /// `self.tag` must be `HostEnvProgramNameResultTag::Ok` and the payload must still be initialized. + #[cfg(not(target_pointer_width = "32"))] + pub unsafe fn borrow_payload_ok_unchecked(&self) -> &UnixBytesOrUtf8OrWindowsU16s { + unsafe { &*(&self.payload.ok as *const core::mem::ManuallyDrop as *const UnixBytesOrUtf8OrWindowsU16s) } + } + + /// Move the `Ok` payload out of one owned tag-union shell. + /// + /// # Safety + /// `self.tag` must be `HostEnvProgramNameResultTag::Ok`. After this call, `self` is logically uninitialized and must not be read or destroyed. + #[cfg(target_pointer_width = "32")] + pub unsafe fn take_payload_ok_unchecked(&mut self) -> UnixBytesOrUtf8OrWindowsU16s { + unsafe { core::ptr::read(self.payload.as_ptr() as *const UnixBytesOrUtf8OrWindowsU16s) } + } + + /// Move the `Ok` payload out of one owned tag-union shell. + /// + /// # Safety + /// `self.tag` must be `HostEnvProgramNameResultTag::Ok`. After this call, `self` is logically uninitialized and must not be read or destroyed. + #[cfg(not(target_pointer_width = "32"))] + pub unsafe fn take_payload_ok_unchecked(&mut self) -> UnixBytesOrUtf8OrWindowsU16s { + unsafe { core::mem::ManuallyDrop::take(&mut self.payload.ok) } + } + +} + +#[cfg(target_pointer_width = "64")] +const _: () = assert!(core::mem::size_of::() == 40, "HostEnvProgramNameResult size mismatch"); +#[cfg(target_pointer_width = "64")] +const _: () = assert!(core::mem::align_of::() == 8, "HostEnvProgramNameResult alignment mismatch"); +#[cfg(target_pointer_width = "64")] +const _: () = assert!(core::mem::offset_of!(HostEnvProgramNameResult, tag) == 32, "HostEnvProgramNameResult tag offset mismatch"); +#[cfg(target_pointer_width = "32")] +const _: () = assert!(core::mem::size_of::() == 20, "HostEnvProgramNameResult size mismatch"); +#[cfg(target_pointer_width = "32")] +const _: () = assert!(core::mem::align_of::() == 4, "HostEnvProgramNameResult alignment mismatch"); +#[cfg(target_pointer_width = "32")] +const _: () = assert!(core::mem::offset_of!(HostEnvProgramNameResult, tag) == 16, "HostEnvProgramNameResult tag offset mismatch"); + /// Tag discriminant for OsStr. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -7861,14 +7952,14 @@ const _: () = assert!(core::mem::offset_of!(OsStr, tag) == 12, "OsStr tag offset /// Tag discriminant for Try. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TryType259Tag { +pub enum TryType267Tag { Err = 0, Ok = 1, } #[repr(C)] #[derive(Clone, Copy)] -pub union TryType259Payload { +pub union TryType267Payload { pub err: core::mem::ManuallyDrop, pub ok: [u8; 0], } @@ -7876,32 +7967,32 @@ pub union TryType259Payload { #[cfg(target_pointer_width = "32")] #[repr(align(4))] #[derive(Clone, Copy)] -pub struct TryType259PayloadAlignment; +pub struct TryType267PayloadAlignment; /// Tag union: Try #[cfg(target_pointer_width = "32")] #[repr(C)] #[derive(Clone, Copy)] -pub struct TryType259 { - pub _payload_alignment: [TryType259PayloadAlignment; 0], +pub struct TryType267 { + pub _payload_alignment: [TryType267PayloadAlignment; 0], pub payload: [u8; 4], - pub tag: TryType259Tag, + pub tag: TryType267Tag, } /// Tag union: Try #[cfg(not(target_pointer_width = "32"))] #[repr(C)] #[derive(Clone, Copy)] -pub struct TryType259 { - pub payload: TryType259Payload, - pub tag: TryType259Tag, +pub struct TryType267 { + pub payload: TryType267Payload, + pub tag: TryType267Tag, } -impl TryType259 { +impl TryType267 { /// Borrow the `Err` payload without creating another owner. /// /// # Safety - /// `self.tag` must be `TryType259Tag::Err` and the payload must still be initialized. + /// `self.tag` must be `TryType267Tag::Err` and the payload must still be initialized. #[cfg(target_pointer_width = "32")] pub unsafe fn borrow_payload_err_unchecked(&self) -> &i32 { unsafe { &*(self.payload.as_ptr() as *const i32) } @@ -7910,7 +8001,7 @@ impl TryType259 { /// Borrow the `Err` payload without creating another owner. /// /// # Safety - /// `self.tag` must be `TryType259Tag::Err` and the payload must still be initialized. + /// `self.tag` must be `TryType267Tag::Err` and the payload must still be initialized. #[cfg(not(target_pointer_width = "32"))] pub unsafe fn borrow_payload_err_unchecked(&self) -> &i32 { unsafe { &*(&self.payload.err as *const core::mem::ManuallyDrop as *const i32) } @@ -7919,7 +8010,7 @@ impl TryType259 { /// Move the `Err` payload out of one owned tag-union shell. /// /// # Safety - /// `self.tag` must be `TryType259Tag::Err`. After this call, `self` is logically uninitialized and must not be read or destroyed. + /// `self.tag` must be `TryType267Tag::Err`. After this call, `self` is logically uninitialized and must not be read or destroyed. #[cfg(target_pointer_width = "32")] pub unsafe fn take_payload_err_unchecked(&mut self) -> i32 { unsafe { core::ptr::read(self.payload.as_ptr() as *const i32) } @@ -7928,7 +8019,7 @@ impl TryType259 { /// Move the `Err` payload out of one owned tag-union shell. /// /// # Safety - /// `self.tag` must be `TryType259Tag::Err`. After this call, `self` is logically uninitialized and must not be read or destroyed. + /// `self.tag` must be `TryType267Tag::Err`. After this call, `self` is logically uninitialized and must not be read or destroyed. #[cfg(not(target_pointer_width = "32"))] pub unsafe fn take_payload_err_unchecked(&mut self) -> i32 { unsafe { core::mem::ManuallyDrop::take(&mut self.payload.err) } @@ -7937,17 +8028,17 @@ impl TryType259 { } #[cfg(target_pointer_width = "64")] -const _: () = assert!(core::mem::size_of::() == 8, "TryType259 size mismatch"); +const _: () = assert!(core::mem::size_of::() == 8, "TryType267 size mismatch"); #[cfg(target_pointer_width = "64")] -const _: () = assert!(core::mem::align_of::() == 4, "TryType259 alignment mismatch"); +const _: () = assert!(core::mem::align_of::() == 4, "TryType267 alignment mismatch"); #[cfg(target_pointer_width = "64")] -const _: () = assert!(core::mem::offset_of!(TryType259, tag) == 4, "TryType259 tag offset mismatch"); +const _: () = assert!(core::mem::offset_of!(TryType267, tag) == 4, "TryType267 tag offset mismatch"); #[cfg(target_pointer_width = "32")] -const _: () = assert!(core::mem::size_of::() == 8, "TryType259 size mismatch"); +const _: () = assert!(core::mem::size_of::() == 8, "TryType267 size mismatch"); #[cfg(target_pointer_width = "32")] -const _: () = assert!(core::mem::align_of::() == 4, "TryType259 alignment mismatch"); +const _: () = assert!(core::mem::align_of::() == 4, "TryType267 alignment mismatch"); #[cfg(target_pointer_width = "32")] -const _: () = assert!(core::mem::offset_of!(TryType259, tag) == 4, "TryType259 tag offset mismatch"); +const _: () = assert!(core::mem::offset_of!(TryType267, tag) == 4, "TryType267 tag offset mismatch"); /// Return type record for Host.env_platform! /// Fields ordered by compiler-emitted ABI offsets. @@ -9178,6 +9269,9 @@ pub type HostTcpAcceptResultTag = HostTcpConnectResultTag; pub type HostTcpListenerCloseResult = HostTcpWriteResult; pub type HostTcpListenerCloseResultPayload = HostTcpWriteResultPayload; pub type HostTcpListenerCloseResultTag = HostTcpWriteResultTag; +pub type HostEnvProgramNameOk = UnixBytesOrUtf8OrWindowsU16s; +pub type HostEnvProgramNameOkPayload = UnixBytesOrUtf8OrWindowsU16sPayload; +pub type HostEnvProgramNameOkTag = UnixBytesOrUtf8OrWindowsU16sTag; pub type MainForHostArg0 = OsStr; pub type MainForHostArg0Payload = OsStrPayload; pub type MainForHostArg0Tag = OsStrTag; @@ -12155,6 +12249,49 @@ unsafe impl RocRelease for HostTcpLocalPortResultRelease } } +impl HostEnvProgramNameResult { + /// Recursively decrement Roc-owned payloads. + /// + /// # Safety + /// `self` must own one live Roc reference for each refcounted payload. + pub unsafe fn decref(self, roc_host: &RocHost) { + let mut value = self; + let _ = roc_host; + match value.tag { + HostEnvProgramNameResultTag::Err => {}, + HostEnvProgramNameResultTag::Ok => { + let payload = unsafe { value.take_payload_ok_unchecked() }; + unsafe { payload.decref(roc_host); } + }, + } + } + + /// Increment Roc-owned payloads. + /// + /// # Safety + /// `self` must point at live Roc allocations. The retained references must + /// be balanced by later decrefs. + pub unsafe fn incref(self, amount: isize) { + let value = self; + let _ = amount; + match value.tag { + HostEnvProgramNameResultTag::Err => {}, + HostEnvProgramNameResultTag::Ok => { + let payload = unsafe { core::ptr::read(value.borrow_payload_ok_unchecked()) }; + unsafe { payload.incref(amount); } + }, + } + } +} + +pub struct HostEnvProgramNameResultRelease; + +unsafe impl RocRelease for HostEnvProgramNameResultRelease { + unsafe fn release(value: HostEnvProgramNameResult, roc_host: &RocHost) { + unsafe { value.decref(roc_host); } + } +} + impl OsStr { /// Recursively decrement Roc-owned payloads. /// @@ -12212,7 +12349,7 @@ unsafe impl RocRelease for OsStrRelease { } } -impl TryType259 { +impl TryType267 { /// Recursively decrement Roc-owned payloads. /// /// # Safety @@ -12221,8 +12358,8 @@ impl TryType259 { let value = self; let _ = roc_host; match value.tag { - TryType259Tag::Err => {}, - TryType259Tag::Ok => {}, + TryType267Tag::Err => {}, + TryType267Tag::Ok => {}, } } @@ -12235,16 +12372,16 @@ impl TryType259 { let value = self; let _ = amount; match value.tag { - TryType259Tag::Err => {}, - TryType259Tag::Ok => {}, + TryType267Tag::Err => {}, + TryType267Tag::Ok => {}, } } } -pub struct TryType259Release; +pub struct TryType267Release; -unsafe impl RocRelease for TryType259Release { - unsafe fn release(value: TryType259, roc_host: &RocHost) { +unsafe impl RocRelease for TryType267Release { + unsafe fn release(value: TryType267, roc_host: &RocHost) { unsafe { value.decref(roc_host); } } } @@ -12980,6 +13117,11 @@ unsafe extern "C" { /// The result is owned by Roc: return exactly one owned reference. pub fn hosted_tcp_listener_close(arg0: *mut u64) -> HostTcpWriteResult; + /// Hosted symbol for Host.env_program_name! + /// Roc signature: {} => Try([UnixBytes(List(U8)), Utf8(Str), WindowsU16s(List(U16))], [ProgramNameUnavailable]) + /// The result is owned by Roc: return exactly one owned reference. + pub fn hosted_env_program_name() -> HostEnvProgramNameResult; + } /// Default memory management functions for Roc platform helpers. diff --git a/tests/resource-lifetimes/process-control.roc b/tests/resource-lifetimes/process-control.roc index 1f4573d1..dede7cf7 100644 --- a/tests/resource-lifetimes/process-control.roc +++ b/tests/resource-lifetimes/process-control.roc @@ -10,7 +10,7 @@ import pf.Stdout main! : List(OsStr) => Try({}, _) main! = |args| { - if args.drop_first(1).map(OsStr.display) == ["--echo-helper"] { + if args.map(OsStr.display) == ["--echo-helper"] { bytes = Stdin.read_to_end!()? Stdout.write_bytes!(bytes)? Ok({})