diff --git a/Cargo.lock b/Cargo.lock index e62916ac0b..2b6e22b589 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5283,7 +5283,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "base64", @@ -5339,14 +5339,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "cc", "libc", @@ -5354,7 +5354,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "log", @@ -5369,7 +5369,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "perry-hir", @@ -5378,7 +5378,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "perry-hir", @@ -5386,7 +5386,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "perry-dispatch", @@ -5396,7 +5396,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "perry-hir", @@ -5405,7 +5405,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "base64", @@ -5418,7 +5418,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "perry-hir", @@ -5426,7 +5426,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "async-trait", @@ -5455,14 +5455,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "serde", "serde_json", @@ -5470,7 +5470,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1173" +version = "0.5.1174" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5481,7 +5481,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "clap", @@ -5496,14 +5496,14 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-argon2" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "argon2", "perry-ffi", @@ -5511,7 +5511,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "reqwest", @@ -5520,7 +5520,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "bcrypt", "perry-ffi", @@ -5528,7 +5528,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "rusqlite", @@ -5536,7 +5536,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "scraper", @@ -5544,7 +5544,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "perry-runtime", @@ -5552,7 +5552,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "chrono", "cron 0.16.0", @@ -5562,7 +5562,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "chrono", "perry-ffi", @@ -5570,7 +5570,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "rust_decimal", @@ -5578,7 +5578,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "serde_json", @@ -5586,7 +5586,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "rand 0.8.6", @@ -5594,7 +5594,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "perry-runtime", @@ -5602,14 +5602,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "bytes", "http-body-util", @@ -5626,7 +5626,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "lazy_static", "perry-ffi", @@ -5638,7 +5638,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "lazy_static", "perry-ext-http-server", @@ -5651,7 +5651,7 @@ dependencies = [ [[package]] name = "perry-ext-http-server" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "bytes", "h2", @@ -5674,7 +5674,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "lazy_static", "perry-ffi", @@ -5684,7 +5684,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "base64", "jsonwebtoken", @@ -5695,7 +5695,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "lru", "perry-ffi", @@ -5703,7 +5703,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "chrono", "perry-ffi", @@ -5711,7 +5711,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "bson", "futures-util", @@ -5723,7 +5723,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "chrono", "perry-ffi", @@ -5733,7 +5733,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "nanoid", "perry-ffi", @@ -5742,7 +5742,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "perry-runtime", @@ -5754,7 +5754,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "lettre", "perry-ffi", @@ -5764,7 +5764,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "printpdf", @@ -5772,7 +5772,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "sqlx", @@ -5781,7 +5781,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "governor", "perry-ffi", @@ -5789,7 +5789,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "base64", "image", @@ -5798,14 +5798,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "lazy_static", "perry-ffi", @@ -5814,7 +5814,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "uuid", @@ -5822,7 +5822,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ffi", "regex", @@ -5832,7 +5832,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "futures-util", "lazy_static", @@ -5844,7 +5844,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "brotli", "flate2", @@ -5853,7 +5853,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "dashmap", "once_cell", @@ -5862,7 +5862,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "perry-api-manifest", @@ -5879,7 +5879,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "perry-diagnostics", @@ -5891,7 +5891,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "base64", @@ -5923,7 +5923,7 @@ dependencies = [ [[package]] name = "perry-stdlib" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "aes 0.8.4", "aes-gcm", @@ -6015,7 +6015,7 @@ dependencies = [ [[package]] name = "perry-transform" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "perry-hir", @@ -6025,7 +6025,7 @@ dependencies = [ [[package]] name = "perry-types" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "anyhow", "thiserror 1.0.69", @@ -6033,14 +6033,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "base64", "itoa", @@ -6057,7 +6057,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "rand 0.8.6", "serde", @@ -6067,7 +6067,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "base64", "cairo-rs", @@ -6090,7 +6090,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "base64", "block2", @@ -6106,7 +6106,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "base64", "block2", @@ -6121,7 +6121,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1173" +version = "0.5.1174" [[package]] name = "perry-ui-test" @@ -6129,11 +6129,11 @@ version = "0.1.0" [[package]] name = "perry-ui-testkit" -version = "0.5.1173" +version = "0.5.1174" [[package]] name = "perry-ui-tvos" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "base64", "block2", @@ -6149,7 +6149,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "base64", "block2", @@ -6165,7 +6165,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "block2", "libc", @@ -6178,7 +6178,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "base64", "libc", @@ -6195,14 +6195,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "base64", "ed25519-dalek", @@ -6216,7 +6216,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1173" +version = "0.5.1174" dependencies = [ "wasmi", ] @@ -9216,7 +9216,9 @@ dependencies = [ "atomic", "getrandom 0.4.2", "js-sys", + "md-5 0.10.6", "serde_core", + "sha1_smol", "wasm-bindgen", ] diff --git a/crates/perry-api-manifest/src/entries.rs b/crates/perry-api-manifest/src/entries.rs index 4b5cb9e159..16f1ac838c 100644 --- a/crates/perry-api-manifest/src/entries.rs +++ b/crates/perry-api-manifest/src/entries.rs @@ -1402,6 +1402,13 @@ pub static API_MANIFEST: &[ApiEntry] = &[ method("commander", "parse", true, None), method("commander", "opts", true, None), method("commander", "argument", true, None), + // `program.args` is a bare member read modeled as a property for the + // `.d.ts` surface (`export const args`), but the dispatch table lowers + // it to a 0-arg instance getter row (`commander::args`, has_receiver). + // The drift gate (every_dispatch_entry_has_manifest_counterpart) wants + // a Method counterpart for that row; keep both — the has_receiver + // method isn't emitted as a module export, so docs are unchanged (#5137). + method("commander", "args", true, None), property("commander", "args"), property("async_hooks", "default"), property("async_hooks", "asyncWrapProviders"), @@ -1473,6 +1480,44 @@ pub static API_MANIFEST: &[ApiEntry] = &[ method_sig("uuid", "v4", false, None, &[], TypeSpec::String), method_sig("uuid", "v1", false, None, &[], TypeSpec::String), method_sig("uuid", "v7", false, None, &[], TypeSpec::String), + method_sig( + "uuid", + "v5", + false, + None, + &[ + ParamSpec::Named { + name: "name", + ty: TypeSpec::String, + optional: false, + }, + ParamSpec::Named { + name: "namespace", + ty: TypeSpec::String, + optional: false, + }, + ], + TypeSpec::String, + ), + method_sig( + "uuid", + "v3", + false, + None, + &[ + ParamSpec::Named { + name: "name", + ty: TypeSpec::String, + optional: false, + }, + ParamSpec::Named { + name: "namespace", + ty: TypeSpec::String, + optional: false, + }, + ], + TypeSpec::String, + ), method_sig( "uuid", "validate", @@ -1485,6 +1530,18 @@ pub static API_MANIFEST: &[ApiEntry] = &[ }], TypeSpec::Bool, ), + method_sig( + "uuid", + "version", + false, + None, + &[ParamSpec::Named { + name: "id", + ty: TypeSpec::String, + optional: false, + }], + TypeSpec::Number, + ), method_sig( "jsonwebtoken", "sign", diff --git a/crates/perry-codegen/src/lower_call/native_module_dispatch.rs b/crates/perry-codegen/src/lower_call/native_module_dispatch.rs index d2765a0448..b22533319b 100644 --- a/crates/perry-codegen/src/lower_call/native_module_dispatch.rs +++ b/crates/perry-codegen/src/lower_call/native_module_dispatch.rs @@ -174,7 +174,7 @@ pub fn lower_native_module_dispatch( | NativeRetKind::Str | NativeRetKind::ObjFromJsonStr | NativeRetKind::BigInt => I64, - NativeRetKind::F64 => DOUBLE, + NativeRetKind::F64 | NativeRetKind::Bool => DOUBLE, NativeRetKind::I32Void => I32, NativeRetKind::Void => crate::types::VOID, }; @@ -272,6 +272,17 @@ pub fn lower_native_module_dispatch( Ok(nanbox_bigint_inline(blk, &raw)) } NativeRetKind::F64 => Ok(ctx.block().call(DOUBLE, sig.runtime, &arg_slices)), + NativeRetKind::Bool => { + // Runtime returns the FFI bool-as-f64 convention (1.0/0.0). + // Box it as a real JS boolean so `===`, `if`, and + // `console.log` see `true`/`false`, not the numbers 1/0. + let blk = ctx.block(); + let raw = blk.call(DOUBLE, sig.runtime, &arg_slices); + let is_true = blk.fcmp("one", &raw, "0.0"); + let true_val = double_literal(f64::from_bits(crate::nanbox::TAG_TRUE)); + let false_val = double_literal(f64::from_bits(crate::nanbox::TAG_FALSE)); + Ok(blk.select(crate::types::I1, &is_true, DOUBLE, &true_val, &false_val)) + } NativeRetKind::I32Void => { let _discard = ctx.block().call(I32, sig.runtime, &arg_slices); Ok(double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))) diff --git a/crates/perry-codegen/src/lower_call/native_table/mod.rs b/crates/perry-codegen/src/lower_call/native_table/mod.rs index 314543e249..9f2a90c800 100644 --- a/crates/perry-codegen/src/lower_call/native_table/mod.rs +++ b/crates/perry-codegen/src/lower_call/native_table/mod.rs @@ -94,6 +94,11 @@ pub(super) enum NativeRetKind { BigInt, /// Returns f64 → pass through (NaN-boxed JSValue). F64, + /// Returns f64 `1.0`/`0.0` → box as a real JS boolean + /// (`TAG_TRUE`/`TAG_FALSE`) so `console.log` prints `true`/`false` + /// rather than `1`/`0`. Use for predicates whose runtime signature + /// returns the FFI bool-as-f64 convention (e.g. `uuid.validate`). + Bool, /// Returns i32 → ignored, return TAG_UNDEFINED. I32Void, /// Returns void → return TAG_UNDEFINED. @@ -129,6 +134,7 @@ pub(super) const NR_STR: NativeRetKind = NativeRetKind::Str; pub(super) const NR_OBJ_FROM_JSON_STR: NativeRetKind = NativeRetKind::ObjFromJsonStr; pub(super) const NR_BIGINT: NativeRetKind = NativeRetKind::BigInt; pub(super) const NR_F64: NativeRetKind = NativeRetKind::F64; +pub(super) const NR_BOOL: NativeRetKind = NativeRetKind::Bool; pub(super) const NR_I32: NativeRetKind = NativeRetKind::I32Void; pub(super) const NR_VOID: NativeRetKind = NativeRetKind::Void; @@ -238,6 +244,7 @@ fn ret_kind_tag(r: &NativeRetKind) -> &'static str { NativeRetKind::ObjFromJsonStr => "NR_OBJ_FROM_JSON_STR", NativeRetKind::BigInt => "NR_BIGINT", NativeRetKind::F64 => "NR_F64", + NativeRetKind::Bool => "NR_BOOL", NativeRetKind::I32Void => "NR_I32", NativeRetKind::Void => "NR_VOID", } diff --git a/crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs b/crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs index 5a6667e8b4..5a2670d16e 100644 --- a/crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs +++ b/crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs @@ -2,6 +2,9 @@ use super::*; pub(super) const UTILS_CRYPTO_ROWS: &[NativeModSig] = &[ // ========== uuid ========== + // All generators return `*mut StringHeader`, so they must box as + // NR_STR (STRING_TAG) — NR_PTR boxed them as a generic native handle + // and `v4()` read back as `[object Object]` (#5197). NativeModSig { module: "uuid", has_receiver: false, @@ -9,7 +12,7 @@ pub(super) const UTILS_CRYPTO_ROWS: &[NativeModSig] = &[ class_filter: None, runtime: "js_uuid_v4", args: &[], - ret: NR_PTR, + ret: NR_STR, }, NativeModSig { module: "uuid", @@ -18,7 +21,7 @@ pub(super) const UTILS_CRYPTO_ROWS: &[NativeModSig] = &[ class_filter: None, runtime: "js_uuid_v1", args: &[], - ret: NR_PTR, + ret: NR_STR, }, NativeModSig { module: "uuid", @@ -27,7 +30,28 @@ pub(super) const UTILS_CRYPTO_ROWS: &[NativeModSig] = &[ class_filter: None, runtime: "js_uuid_v7", args: &[], - ret: NR_PTR, + ret: NR_STR, + }, + // v5 (SHA-1) / v3 (MD5) name-based: `vN(name, namespace)`. The shim + // supports the string-UUID namespace form; the array-namespace form + // is only reachable via `perry.compilePackages`. + NativeModSig { + module: "uuid", + has_receiver: false, + method: "v5", + class_filter: None, + runtime: "js_uuid_v5", + args: &[NA_STR, NA_STR], + ret: NR_STR, + }, + NativeModSig { + module: "uuid", + has_receiver: false, + method: "v3", + class_filter: None, + runtime: "js_uuid_v3", + args: &[NA_STR, NA_STR], + ret: NR_STR, }, NativeModSig { module: "uuid", @@ -35,7 +59,20 @@ pub(super) const UTILS_CRYPTO_ROWS: &[NativeModSig] = &[ method: "validate", class_filter: None, runtime: "js_uuid_validate", - args: &[NA_F64], + // Runtime sig is `*const StringHeader` → coerce the arg to a + // string pointer (NA_F64 passed raw NaN-box bits, so validate + // always read 0 — #5197). NR_BOOL boxes the 1.0/0.0 result as a + // real JS boolean so it prints `true`/`false`, not `1`/`0`. + args: &[NA_STR], + ret: NR_BOOL, + }, + NativeModSig { + module: "uuid", + has_receiver: false, + method: "version", + class_filter: None, + runtime: "js_uuid_version", + args: &[NA_STR], ret: NR_F64, }, // ========== jsonwebtoken ========== diff --git a/crates/perry-ext-uuid/Cargo.toml b/crates/perry-ext-uuid/Cargo.toml index bfd171cf1e..8caa9f30b5 100644 --- a/crates/perry-ext-uuid/Cargo.toml +++ b/crates/perry-ext-uuid/Cargo.toml @@ -10,7 +10,7 @@ crate-type = ["staticlib", "rlib"] [dependencies] perry-ffi.workspace = true -uuid = { version = "1.23", features = ["v1", "v4", "v7"] } +uuid = { version = "1.23", features = ["v1", "v3", "v4", "v5", "v7"] } [dev-dependencies] perry-ffi = { workspace = true, features = ["runtime-link"] } diff --git a/crates/perry-ext-uuid/src/lib.rs b/crates/perry-ext-uuid/src/lib.rs index 862428829c..cded359210 100644 --- a/crates/perry-ext-uuid/src/lib.rs +++ b/crates/perry-ext-uuid/src/lib.rs @@ -29,6 +29,53 @@ pub extern "C" fn js_uuid_v7() -> *mut StringHeader { alloc_string(&uuid.to_string()).as_raw() } +/// Parse a NaN-boxed namespace argument into a `Uuid`. The shim only +/// supports the string-UUID namespace form (`v5(name, '6ba7…')`), which +/// covers the `uuid.v5.DNS`/`uuid.v5.URL` constants and the overwhelming +/// majority of real usage. A non-string / unparseable namespace falls +/// back to the nil UUID rather than crashing — the array-namespace form +/// is only reachable via `perry.compilePackages` (real source). +unsafe fn parse_namespace(ns_ptr: *const StringHeader) -> Uuid { + let handle = JsString::from_raw(ns_ptr as *mut StringHeader); + read_string(handle) + .and_then(|s| Uuid::parse_str(s).ok()) + .unwrap_or_else(Uuid::nil) +} + +/// `uuid.v5(name, namespace)` — SHA-1 name-based UUID. +/// +/// # Safety +/// +/// `name_ptr` / `ns_ptr` must be null or Perry-runtime `StringHeader` +/// pointers. +#[no_mangle] +pub unsafe extern "C" fn js_uuid_v5( + name_ptr: *const StringHeader, + ns_ptr: *const StringHeader, +) -> *mut StringHeader { + let name = read_string(JsString::from_raw(name_ptr as *mut StringHeader)).unwrap_or(""); + let namespace = parse_namespace(ns_ptr); + let uuid = Uuid::new_v5(&namespace, name.as_bytes()); + alloc_string(&uuid.to_string()).as_raw() +} + +/// `uuid.v3(name, namespace)` — MD5 name-based UUID. +/// +/// # Safety +/// +/// `name_ptr` / `ns_ptr` must be null or Perry-runtime `StringHeader` +/// pointers. +#[no_mangle] +pub unsafe extern "C" fn js_uuid_v3( + name_ptr: *const StringHeader, + ns_ptr: *const StringHeader, +) -> *mut StringHeader { + let name = read_string(JsString::from_raw(name_ptr as *mut StringHeader)).unwrap_or(""); + let namespace = parse_namespace(ns_ptr); + let uuid = Uuid::new_v3(&namespace, name.as_bytes()); + alloc_string(&uuid.to_string()).as_raw() +} + /// `uuid.validate(str) -> boolean` — encoded as `1.0` / `0.0` /// because the Perry FFI ABI carries booleans as f64. /// @@ -125,4 +172,35 @@ mod tests { let s = read_handle(js_uuid_nil()); assert_eq!(s, "00000000-0000-0000-0000-000000000000"); } + + #[test] + fn v5_matches_the_reference_vector() { + // `v5('perry', '6ba7b810-9dad-11d1-80b4-00c04fd430c8')` — the + // exact value Node's `uuid` produces (issue #5197). + let name = alloc_string("perry"); + let ns = alloc_string("6ba7b810-9dad-11d1-80b4-00c04fd430c8"); + let id = + read_handle(unsafe { js_uuid_v5(name.as_raw() as *const _, ns.as_raw() as *const _) }); + assert_eq!(id, "6cb3836f-339d-52d8-acc6-8751229b61cf"); + let id_handle = alloc_string(&id); + assert_eq!( + unsafe { js_uuid_version(id_handle.as_raw() as *const _) }, + 5.0 + ); + } + + #[test] + fn v3_matches_the_reference_vector() { + // `v3('perry', '6ba7b810-9dad-11d1-80b4-00c04fd430c8')`. + let name = alloc_string("perry"); + let ns = alloc_string("6ba7b810-9dad-11d1-80b4-00c04fd430c8"); + let id = + read_handle(unsafe { js_uuid_v3(name.as_raw() as *const _, ns.as_raw() as *const _) }); + assert_eq!(id, "3533df6e-72b1-3859-a772-7410b3d2f9c2"); + let id_handle = alloc_string(&id); + assert_eq!( + unsafe { js_uuid_version(id_handle.as_raw() as *const _) }, + 3.0 + ); + } } diff --git a/crates/perry-stdlib/Cargo.toml b/crates/perry-stdlib/Cargo.toml index b1ed3fbc7e..9739850925 100644 --- a/crates/perry-stdlib/Cargo.toml +++ b/crates/perry-stdlib/Cargo.toml @@ -450,7 +450,7 @@ validator = { version = "0.20", features = ["derive"], optional = true } regex = { version = "1.12", optional = true } # IDs -uuid = { version = "1.23", features = ["v4", "v1", "v7"], optional = true } +uuid = { version = "1.23", features = ["v4", "v1", "v3", "v5", "v7"], optional = true } nanoid = { version = "0.5", optional = true } # LRU Cache — optional from v0.5.539 so the well-known flip can diff --git a/crates/perry-stdlib/src/uuid.rs b/crates/perry-stdlib/src/uuid.rs index 4933d97756..dc29de8084 100644 --- a/crates/perry-stdlib/src/uuid.rs +++ b/crates/perry-stdlib/src/uuid.rs @@ -37,6 +37,50 @@ pub extern "C" fn js_uuid_v7() -> *mut StringHeader { js_string_from_bytes(uuid_str.as_ptr(), uuid_str.len() as u32) } +/// Read a `*const StringHeader` into a `&str`, or `""` if null / invalid UTF-8. +unsafe fn read_str<'a>(str_ptr: *const StringHeader) -> &'a str { + if str_ptr.is_null() { + return ""; + } + let len = (*str_ptr).byte_len as usize; + let data_ptr = (str_ptr as *const u8).add(std::mem::size_of::()); + let bytes = std::slice::from_raw_parts(data_ptr, len); + std::str::from_utf8(bytes).unwrap_or("") +} + +/// Parse a namespace argument (string-UUID form) into a `Uuid`, falling +/// back to the nil UUID when the argument isn't a parseable UUID string. +/// The array-namespace form is only reachable via `perry.compilePackages`. +unsafe fn parse_namespace(ns_ptr: *const StringHeader) -> Uuid { + Uuid::parse_str(read_str(ns_ptr)).unwrap_or_else(|_| Uuid::nil()) +} + +/// Generate a v5 (SHA-1 name-based) UUID and return it as a string +/// uuid.v5(name, namespace) -> string +#[no_mangle] +pub unsafe extern "C" fn js_uuid_v5( + name_ptr: *const StringHeader, + ns_ptr: *const StringHeader, +) -> *mut StringHeader { + let namespace = parse_namespace(ns_ptr); + let uuid = Uuid::new_v5(&namespace, read_str(name_ptr).as_bytes()); + let uuid_str = uuid.to_string(); + js_string_from_bytes(uuid_str.as_ptr(), uuid_str.len() as u32) +} + +/// Generate a v3 (MD5 name-based) UUID and return it as a string +/// uuid.v3(name, namespace) -> string +#[no_mangle] +pub unsafe extern "C" fn js_uuid_v3( + name_ptr: *const StringHeader, + ns_ptr: *const StringHeader, +) -> *mut StringHeader { + let namespace = parse_namespace(ns_ptr); + let uuid = Uuid::new_v3(&namespace, read_str(name_ptr).as_bytes()); + let uuid_str = uuid.to_string(); + js_string_from_bytes(uuid_str.as_ptr(), uuid_str.len() as u32) +} + /// Validate if a string is a valid UUID /// uuid.validate(str) -> boolean #[no_mangle] diff --git a/docs/api/perry.d.ts b/docs/api/perry.d.ts index dcb74e9709..86eddf61f3 100644 --- a/docs/api/perry.d.ts +++ b/docs/api/perry.d.ts @@ -1,6 +1,6 @@ // Auto-generated from Perry's API manifest (#465). Do not edit by hand. // Source: perry-api-manifest::API_MANIFEST -// Coverage: 1940 entries across 113 modules +// Coverage: 1943 entries across 113 modules type PerryU32 = number & { readonly __perryU32?: never }; type PerryU64 = number & { readonly __perryU64?: never }; @@ -3985,11 +3985,17 @@ declare module "uuid" { /** stdlib */ export function v1(): string; /** stdlib */ + export function v3(name: string, namespace: string): string; + /** stdlib */ export function v4(): string; /** stdlib */ + export function v5(name: string, namespace: string): string; + /** stdlib */ export function v7(): string; /** stdlib */ export function validate(id: string): boolean; + /** stdlib */ + export function version(id: string): number; } declare module "v8" { diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index 6ce3f1cd87..7bbf9ffc04 100644 --- a/docs/src/api/reference.md +++ b/docs/src/api/reference.md @@ -2,7 +2,7 @@ This page is auto-generated from Perry's compile-time API manifest (`perry-api-manifest::API_MANIFEST`). It is the source of truth for what `perry compile` accepts; references to symbols not listed here produce `R005 UnimplementedApi` (issue #463). Stubs (#464) are flagged ⚠ — they link cleanly but no-op at runtime on the chosen target. -Total: 2804 entries across 115 modules. +Total: 2808 entries across 115 modules. ## Modules @@ -394,6 +394,7 @@ Total: 2804 entries across 115 modules. ### Methods - `action` — instance +- `args` — instance - `argument` — instance - `command` — instance - `description` — instance @@ -3524,9 +3525,12 @@ Total: 2804 entries across 115 modules. ### Methods - `v1` — module +- `v3` — module - `v4` — module +- `v5` — module - `v7` — module - `validate` — module +- `version` — module ## `v8`