@tishlang/tish-lint 2.43.8 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/tish-lint +0 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +21 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/tish.d.tish +4 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +5 -1
- package/crates/tish/tests/regr_typed_externs.rs +59 -0
- package/crates/tish/tests/scope_merge.rs +31 -0
- package/crates/tish_build_utils/src/lib.rs +131 -6
- package/crates/tish_builtins/src/globals.rs +27 -0
- package/crates/tish_compile/src/codegen.rs +1498 -132
- package/crates/tish_compile/src/resolve.rs +65 -9
- package/crates/tish_compile/src/schemes.rs +60 -5
- package/crates/tish_compile/src/types.rs +64 -32
- package/crates/tish_compile/tests/regr_556_param_name_collision.rs +40 -0
- package/crates/tish_compile/tests/regr_558_struct_array_write.rs +65 -0
- package/crates/tish_compile/tests/regr_558b_struct_field_read.rs +21 -0
- package/crates/tish_compile/tests/regr_558c_wrapped_struct_array.rs +64 -0
- package/crates/tish_compile/tests/regr_558d_native_vec_mutators.rs +156 -0
- package/crates/tish_compile/tests/regr_562_wrapped_struct_array_push.rs +50 -0
- package/crates/tish_compile/tests/regr_fixed_native_coercions.rs +69 -0
- package/crates/tish_compile/tests/regr_gba_struct_reflection.rs +106 -0
- package/crates/tish_compile/tests/regression/name_collision_lib.tish +6 -0
- package/crates/tish_compile/tests/regression/name_collision_main.tish +13 -0
- package/crates/tish_compile/tests/regression/struct_array_write.tish +22 -0
- package/crates/tish_compile/tests/regression/struct_field_return_read.tish +10 -0
- package/crates/tish_core/src/console_style.rs +4 -0
- package/crates/tish_core/src/json.rs +8 -0
- package/crates/tish_core/src/shape.rs +28 -3
- package/crates/tish_core/src/value.rs +123 -0
- package/crates/tish_lint/Cargo.toml +1 -1
- package/crates/tish_native/src/build.rs +4 -0
- package/crates/tish_runtime/Cargo.toml +20 -1
- package/crates/tish_runtime/src/crypto.rs +127 -0
- package/crates/tish_runtime/src/encoding.rs +157 -0
- package/crates/tish_runtime/src/fs_ext.rs +27 -0
- package/crates/tish_runtime/src/lib.rs +33 -0
- package/crates/tish_runtime/src/net.rs +482 -0
- package/crates/tish_runtime/src/process_spawn.rs +508 -0
- package/crates/tish_runtime/src/ws.rs +206 -23
- package/crates/tish_runtime/src/zip.rs +196 -0
- package/crates/tish_runtime_gba/src/lib.rs +188 -4
- package/crates/tish_vm/src/vm.rs +48 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-lint +0 -0
- package/platform/darwin-x64/tish-lint +0 -0
- package/platform/linux-arm64/tish-lint +0 -0
- package/platform/linux-x64/tish-lint +0 -0
- package/platform/win32-x64/tish-lint.exe +0 -0
package/bin/tish-lint
CHANGED
|
Binary file
|
|
@@ -10,3 +10,24 @@ pub fn greet(args: &[Value]) -> Value {
|
|
|
10
10
|
};
|
|
11
11
|
Value::String(Arc::from(format!("Hello, {}!", name)))
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
/// Boxed shim kept for dynamic call sites (`Value::Function` dispatch / a spread call).
|
|
15
|
+
pub fn add(args: &[Value]) -> Value {
|
|
16
|
+
let a = match args.first() {
|
|
17
|
+
Some(Value::Number(n)) => *n,
|
|
18
|
+
_ => 0.0,
|
|
19
|
+
};
|
|
20
|
+
let b = match args.get(1) {
|
|
21
|
+
Some(Value::Number(n)) => *n,
|
|
22
|
+
_ => 0.0,
|
|
23
|
+
};
|
|
24
|
+
Value::Number(a + b)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Typed extern (`declare fn add(a: number, b: number): number` in `tish.d.tish`): the direct, unboxed
|
|
28
|
+
/// entry codegen calls as `demo_shim::add_typed(..)` when the args lower to the declared native types.
|
|
29
|
+
/// `number` lowers to `f64` on every native target (unlike `i32`, which is native only under
|
|
30
|
+
/// `--target gba`), so this direct call fires on the default desktop build too.
|
|
31
|
+
pub fn add_typed(a: f64, b: f64) -> f64 {
|
|
32
|
+
a + b
|
|
33
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Typed externs shipped by the `demo_shim` crate (auto-loaded from beside its source by codegen).
|
|
2
|
+
// Declares the native signatures of exports so a call lowers to a DIRECT `demo_shim::<name>_typed(..)`
|
|
3
|
+
// instead of the boxed `value_call` path — the consuming program never writes these declarations.
|
|
4
|
+
declare fn add(a: number, b: number): number
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
import { greet } from 'cargo:demo_shim'
|
|
1
|
+
import { greet, add } from 'cargo:demo_shim'
|
|
2
2
|
|
|
3
3
|
console.log(greet('from cargo'))
|
|
4
|
+
|
|
5
|
+
// `add` has a typed signature shipped in the crate's `tish.d.tish` (auto-loaded) — this call lowers
|
|
6
|
+
// to a direct `demo_shim::add_typed(2, 3)`, not the boxed namespace-object dispatch.
|
|
7
|
+
console.log(add(2, 3))
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//! Typed externs (perf lever): a native `cargo:` import whose typed signature is shipped in the
|
|
2
|
+
//! crate's `tish.d.tish` (auto-loaded from beside the crate source) must lower a matching call to a
|
|
3
|
+
//! DIRECT `<crate>::<name>_typed(..)` — no `Value` boxing of the args, no `value_call` dispatch —
|
|
4
|
+
//! while a call with no typed signature keeps the boxed namespace path. Uses the in-repo
|
|
5
|
+
//! `tests/fixtures/cargo_example_project/` fixture: `demo_shim` exports `greet` (untyped, stays boxed)
|
|
6
|
+
//! and `add` (declared `add(a: i32, b: i32): i32` in `crates/demo-shim/tish.d.tish`).
|
|
7
|
+
//!
|
|
8
|
+
//! Covers the three branch pieces with no other coverage: the `tish.d.tish` auto-load
|
|
9
|
+
//! (`compile_project_full_emit`), `collect_extern_fns` pairing, and the direct-call emission.
|
|
10
|
+
|
|
11
|
+
use std::path::PathBuf;
|
|
12
|
+
|
|
13
|
+
use tishlang_compile::compile_project_full;
|
|
14
|
+
|
|
15
|
+
fn emit_fixture() -> String {
|
|
16
|
+
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
17
|
+
let input_path = manifest_dir
|
|
18
|
+
.join("tests/fixtures/cargo_example_project/src/main.tish")
|
|
19
|
+
.canonicalize()
|
|
20
|
+
.expect("cargo_example_project test fixture");
|
|
21
|
+
let project_root = input_path.parent().map(|p| {
|
|
22
|
+
if p.file_name().and_then(|n| n.to_str()) == Some("src") {
|
|
23
|
+
p.parent().unwrap_or(p)
|
|
24
|
+
} else {
|
|
25
|
+
p
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
compile_project_full(&input_path, project_root, &[], true)
|
|
29
|
+
.expect("compile_project_full on the cargo example fixture")
|
|
30
|
+
.0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[test]
|
|
34
|
+
fn declared_extern_call_lowers_to_a_direct_typed_call() {
|
|
35
|
+
let rust = emit_fixture();
|
|
36
|
+
// The `add(2, 3)` call (typed via the auto-loaded tish.d.tish) must be a direct call into the
|
|
37
|
+
// crate's `add_typed`, not a boxed dispatch through the namespace object.
|
|
38
|
+
assert!(
|
|
39
|
+
rust.contains("add_typed"),
|
|
40
|
+
"typed extern `add` must lower to a direct `demo_shim::add_typed(..)` call; \
|
|
41
|
+
the auto-loaded tish.d.tish declaration was not honored.\n{rust}"
|
|
42
|
+
);
|
|
43
|
+
// The direct call targets the backing crate.
|
|
44
|
+
assert!(
|
|
45
|
+
rust.contains("demo_shim :: add_typed") || rust.contains("demo_shim::add_typed"),
|
|
46
|
+
"the direct typed-extern call must target the `demo_shim` crate.\n{rust}"
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#[test]
|
|
51
|
+
fn untyped_extern_call_stays_boxed() {
|
|
52
|
+
let rust = emit_fixture();
|
|
53
|
+
// `greet` has no typed signature (no tish.d.tish entry), so it must NOT gain a `_typed` call —
|
|
54
|
+
// it keeps the boxed namespace/`value_call` path. Guards against collect_extern_fns over-matching.
|
|
55
|
+
assert!(
|
|
56
|
+
!rust.contains("greet_typed"),
|
|
57
|
+
"untyped extern `greet` must stay boxed (no `greet_typed` direct call).\n{rust}"
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -77,3 +77,34 @@ fn scope_exports_tish_merges_as_source() {
|
|
|
77
77
|
fn scope_plain_main_merges_as_source() {
|
|
78
78
|
assert_merged_as_source(&setup(r#","main":"index.tish""#));
|
|
79
79
|
}
|
|
80
|
+
|
|
81
|
+
/// The other half of the #38 split: a `@scope/pkg` that opts in via `tish.crate` (the tish-apple
|
|
82
|
+
/// hosts, published as `@tishlang/tish-macos` / `@tishlang/tish-ios`) routes to the NATIVE path —
|
|
83
|
+
/// lowered to `NativeModuleLoad`, not merged as source.
|
|
84
|
+
#[test]
|
|
85
|
+
fn scope_tish_crate_routes_native() {
|
|
86
|
+
let dir = setup(r#","tish":{"module":true,"crate":"test-greet","export":"greet_object"}"#);
|
|
87
|
+
let root = dir.path();
|
|
88
|
+
let modules = resolve_project(&root.join("main.tish"), Some(root)).expect("resolve");
|
|
89
|
+
let merged = merge_modules(modules).expect("merge");
|
|
90
|
+
let native_scope = merged.program.statements.iter().any(|s| {
|
|
91
|
+
matches!(
|
|
92
|
+
s,
|
|
93
|
+
Statement::VarDecl { init: Some(Expr::NativeModuleLoad { spec, .. }), .. }
|
|
94
|
+
if spec.as_ref() == "@test/greet"
|
|
95
|
+
)
|
|
96
|
+
});
|
|
97
|
+
assert!(
|
|
98
|
+
native_scope,
|
|
99
|
+
"@scope package with tish.crate must lower to NativeModuleLoad (native Rust-crate path)"
|
|
100
|
+
);
|
|
101
|
+
let has_greet = merged
|
|
102
|
+
.program
|
|
103
|
+
.statements
|
|
104
|
+
.iter()
|
|
105
|
+
.any(|s| matches!(s, Statement::FunDecl { name, .. } if name.as_ref() == "greet"));
|
|
106
|
+
assert!(
|
|
107
|
+
!has_greet,
|
|
108
|
+
"@scope package with tish.crate must NOT be merged as Tish source"
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
//! Provides workspace discovery, path resolution, and Cargo build orchestration
|
|
4
4
|
//! used by tishlang_wasm, tishlang_cranelift, tishlang_native, and the tish CLI.
|
|
5
5
|
|
|
6
|
+
use std::collections::BTreeSet;
|
|
6
7
|
use std::fs;
|
|
7
8
|
use std::path::{Path, PathBuf};
|
|
8
9
|
use std::process::Command;
|
|
9
|
-
use std::sync::Mutex;
|
|
10
|
+
use std::sync::{Mutex, OnceLock};
|
|
10
11
|
|
|
11
12
|
/// Serialize nested `cargo build` calls that share the workspace `target/` dir (tests + `tish build`).
|
|
12
13
|
static NESTED_CARGO_MUTEX: Mutex<()> = Mutex::new(());
|
|
@@ -19,6 +20,56 @@ fn mold_available() -> bool {
|
|
|
19
20
|
.unwrap_or(false)
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/// The `target_feature="..."` values in `rustc --print cfg` output.
|
|
24
|
+
fn parse_target_features(print_cfg_stdout: &str) -> BTreeSet<String> {
|
|
25
|
+
print_cfg_stdout
|
|
26
|
+
.lines()
|
|
27
|
+
.filter_map(|l| l.trim().strip_prefix("target_feature=\""))
|
|
28
|
+
.filter_map(|l| l.strip_suffix('"'))
|
|
29
|
+
.map(str::to_owned)
|
|
30
|
+
.collect()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Host target features rustc reports, optionally under one extra `-C` flag.
|
|
34
|
+
fn rustc_target_features(codegen_flag: Option<&str>) -> Option<BTreeSet<String>> {
|
|
35
|
+
let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
|
|
36
|
+
let mut cmd = Command::new(rustc);
|
|
37
|
+
cmd.args(["--print", "cfg"]);
|
|
38
|
+
if let Some(flag) = codegen_flag {
|
|
39
|
+
cmd.args(["-C", flag]);
|
|
40
|
+
}
|
|
41
|
+
let out = cmd.output().ok()?;
|
|
42
|
+
if !out.status.success() {
|
|
43
|
+
return None;
|
|
44
|
+
}
|
|
45
|
+
Some(parse_target_features(&String::from_utf8_lossy(&out.stdout)))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// True when `-C target-cpu=native` is worth passing on this host.
|
|
49
|
+
///
|
|
50
|
+
/// `native` asks LLVM to identify the host core. On hosts it cannot identify — notably
|
|
51
|
+
/// the virtualized Apple Silicon behind GitHub Actions' macOS runners — it answers
|
|
52
|
+
/// `generic`, which is *narrower* than the target's own default CPU. On
|
|
53
|
+
/// aarch64-apple-darwin that silently drops `aes` and `sha2`, and crates that assert
|
|
54
|
+
/// those are statically present then fail to compile at all; `ring` 0.17 trips
|
|
55
|
+
/// `assertion failed: CAPS_STATIC == MIN_STATIC_FEATURES` in const-eval.
|
|
56
|
+
///
|
|
57
|
+
/// So this compares feature sets rather than trusting `native` blindly: keep the flag
|
|
58
|
+
/// only when it is a strict win (every default feature survives). If rustc cannot be
|
|
59
|
+
/// queried, skip the flag — the target default is always correct, just less tuned.
|
|
60
|
+
fn native_target_cpu_is_safe() -> bool {
|
|
61
|
+
static SAFE: OnceLock<bool> = OnceLock::new();
|
|
62
|
+
*SAFE.get_or_init(|| {
|
|
63
|
+
match (
|
|
64
|
+
rustc_target_features(None),
|
|
65
|
+
rustc_target_features(Some("target-cpu=native")),
|
|
66
|
+
) {
|
|
67
|
+
(Some(default), Some(native)) => default.is_subset(&native),
|
|
68
|
+
_ => false,
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
22
73
|
/// True if `root` looks like the Tish language repo (has `crates/tish_runtime`).
|
|
23
74
|
///
|
|
24
75
|
/// Used so we do not treat unrelated workspaces (e.g. a parent `zectre-platform` repo) as Tish
|
|
@@ -347,7 +398,13 @@ pub fn cargo_target_name(stem: &str) -> String {
|
|
|
347
398
|
// A leading digit is prefixed with `_`; an empty result falls back to a fixed default.
|
|
348
399
|
let mut name: String = stem
|
|
349
400
|
.chars()
|
|
350
|
-
.map(|c|
|
|
401
|
+
.map(|c| {
|
|
402
|
+
if c.is_ascii_alphanumeric() || c == '_' {
|
|
403
|
+
c
|
|
404
|
+
} else {
|
|
405
|
+
'_'
|
|
406
|
+
}
|
|
407
|
+
})
|
|
351
408
|
.collect();
|
|
352
409
|
if name.is_empty() {
|
|
353
410
|
return "tish_output".to_string();
|
|
@@ -371,6 +428,10 @@ pub fn create_build_dir(prefix: &str, out_name: &str) -> Result<PathBuf, String>
|
|
|
371
428
|
}
|
|
372
429
|
|
|
373
430
|
/// Run cargo build in the given directory.
|
|
431
|
+
///
|
|
432
|
+
/// `-C target-cpu=native` is added only for host builds, only when the caller has not
|
|
433
|
+
/// already pinned a `target-cpu` in `RUSTFLAGS`, and only when `native` does not lose
|
|
434
|
+
/// target features against the target default (see `native_target_cpu_is_safe`).
|
|
374
435
|
/// If `cross_target` is Some, passes `--target` and skips `-C target-cpu=native`.
|
|
375
436
|
pub fn run_cargo_build(
|
|
376
437
|
build_dir: &Path,
|
|
@@ -395,10 +456,14 @@ pub fn run_cargo_build(
|
|
|
395
456
|
merged_rustflags = format!("{} -C link-arg=-fuse-ld=mold", merged_rustflags.trim());
|
|
396
457
|
merged_rustflags = merged_rustflags.trim().to_string();
|
|
397
458
|
}
|
|
398
|
-
} else if merged_rustflags.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
merged_rustflags =
|
|
459
|
+
} else if !merged_rustflags.contains("target-cpu") && native_target_cpu_is_safe() {
|
|
460
|
+
// Caller did not pin a CPU and `native` resolves to something at least as
|
|
461
|
+
// capable as the target default — see `native_target_cpu_is_safe`.
|
|
462
|
+
merged_rustflags = if merged_rustflags.is_empty() {
|
|
463
|
+
"-C target-cpu=native".to_string()
|
|
464
|
+
} else {
|
|
465
|
+
format!("{} -C target-cpu=native", merged_rustflags)
|
|
466
|
+
};
|
|
402
467
|
}
|
|
403
468
|
|
|
404
469
|
let mut cmd = Command::new("cargo");
|
|
@@ -438,6 +503,66 @@ pub fn run_cargo_build(
|
|
|
438
503
|
mod protoc_tests {
|
|
439
504
|
use super::*;
|
|
440
505
|
|
|
506
|
+
/// Trimmed `rustc --print cfg --target aarch64-apple-darwin`.
|
|
507
|
+
const APPLE_DARWIN_DEFAULT_CFG: &str = r#"
|
|
508
|
+
debug_assertions
|
|
509
|
+
panic="unwind"
|
|
510
|
+
target_arch="aarch64"
|
|
511
|
+
target_feature="aes"
|
|
512
|
+
target_feature="neon"
|
|
513
|
+
target_feature="sha2"
|
|
514
|
+
target_feature="sha3"
|
|
515
|
+
target_os="macos"
|
|
516
|
+
"#;
|
|
517
|
+
|
|
518
|
+
#[test]
|
|
519
|
+
fn parses_target_features_from_print_cfg() {
|
|
520
|
+
let features = parse_target_features(APPLE_DARWIN_DEFAULT_CFG);
|
|
521
|
+
assert_eq!(
|
|
522
|
+
features.iter().map(String::as_str).collect::<Vec<_>>(),
|
|
523
|
+
["aes", "neon", "sha2", "sha3"]
|
|
524
|
+
);
|
|
525
|
+
// Non-`target_feature` keys never leak in, and neither do the quotes.
|
|
526
|
+
assert!(!features.contains("aarch64"));
|
|
527
|
+
assert!(!features.contains("\"aes\""));
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
#[test]
|
|
531
|
+
fn native_cpu_rejected_when_it_drops_target_features() {
|
|
532
|
+
// What GitHub's virtualized Apple Silicon reports: `native` -> `generic`, which
|
|
533
|
+
// keeps only `neon`. Pinning it would break `ring`'s static feature assertions.
|
|
534
|
+
let default = parse_target_features(APPLE_DARWIN_DEFAULT_CFG);
|
|
535
|
+
let generic = parse_target_features("target_feature=\"neon\"\n");
|
|
536
|
+
assert!(!default.is_subset(&generic));
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
#[test]
|
|
540
|
+
fn native_cpu_accepted_when_it_only_adds() {
|
|
541
|
+
let default = parse_target_features(APPLE_DARWIN_DEFAULT_CFG);
|
|
542
|
+
let apple_m1 = parse_target_features(
|
|
543
|
+
"target_feature=\"aes\"\ntarget_feature=\"neon\"\n\
|
|
544
|
+
target_feature=\"sha2\"\ntarget_feature=\"sha3\"\ntarget_feature=\"lse\"\n",
|
|
545
|
+
);
|
|
546
|
+
assert!(default.is_subset(&apple_m1));
|
|
547
|
+
// Identical sets count as safe too — `native` matching the default is a no-op.
|
|
548
|
+
assert!(default.is_subset(&default));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
#[test]
|
|
552
|
+
fn live_rustc_probe_still_parses() {
|
|
553
|
+
// Guards the parse against a `rustc --print cfg` format change: every target Tish
|
|
554
|
+
// builds on has at least one baseline target feature (x86_64 sse2, aarch64 neon).
|
|
555
|
+
// If this ever comes back empty the probe would silently disable target-cpu=native
|
|
556
|
+
// everywhere rather than only on hosts LLVM cannot identify.
|
|
557
|
+
let Some(features) = rustc_target_features(None) else {
|
|
558
|
+
return; // no rustc on PATH — nothing to assert
|
|
559
|
+
};
|
|
560
|
+
assert!(
|
|
561
|
+
!features.is_empty(),
|
|
562
|
+
"no target_feature in rustc --print cfg"
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
|
|
441
566
|
#[test]
|
|
442
567
|
fn cargo_target_name_replaces_hyphens() {
|
|
443
568
|
assert_eq!(cargo_target_name("hello-ios"), "hello_ios");
|
|
@@ -354,12 +354,23 @@ pub fn object_keys(args: &[Value]) -> Value {
|
|
|
354
354
|
.collect();
|
|
355
355
|
Value::Array(VmRef::new(keys))
|
|
356
356
|
}
|
|
357
|
+
// A by-reference boxed typed struct (GBA/portable): materialise it to its ordered object and
|
|
358
|
+
// enumerate that, so `Object.keys(s)` / `for (k in s)` (lowered to `for-of Object.keys`) see
|
|
359
|
+
// the field names instead of nothing. (JSON/display already materialise; this closes the
|
|
360
|
+
// reflection paths so the boxed struct is enumerable exactly like the object it stands for.)
|
|
361
|
+
#[cfg(feature = "portable")]
|
|
362
|
+
Some(Value::Struct(s)) => object_keys(&[s.borrow().tish_to_object()]),
|
|
357
363
|
_ => Value::Array(VmRef::new(Vec::new())),
|
|
358
364
|
}
|
|
359
365
|
}
|
|
360
366
|
|
|
361
367
|
/// Object.values(obj)
|
|
362
368
|
pub fn object_values(args: &[Value]) -> Value {
|
|
369
|
+
// A boxed typed struct materialises to its ordered object first (see `object_keys`).
|
|
370
|
+
#[cfg(feature = "portable")]
|
|
371
|
+
if let Some(Value::Struct(s)) = args.first() {
|
|
372
|
+
return object_values(&[s.borrow().tish_to_object()]);
|
|
373
|
+
}
|
|
363
374
|
if let Some(Value::Object(obj)) = args.first() {
|
|
364
375
|
let obj_borrow = obj.borrow();
|
|
365
376
|
let values: Vec<Value> = obj_borrow.strings.values().cloned().collect();
|
|
@@ -371,6 +382,11 @@ pub fn object_values(args: &[Value]) -> Value {
|
|
|
371
382
|
|
|
372
383
|
/// Object.entries(obj)
|
|
373
384
|
pub fn object_entries(args: &[Value]) -> Value {
|
|
385
|
+
// A boxed typed struct materialises to its ordered object first (see `object_keys`).
|
|
386
|
+
#[cfg(feature = "portable")]
|
|
387
|
+
if let Some(Value::Struct(s)) = args.first() {
|
|
388
|
+
return object_entries(&[s.borrow().tish_to_object()]);
|
|
389
|
+
}
|
|
374
390
|
if let Some(Value::Object(obj)) = args.first() {
|
|
375
391
|
let obj_borrow = obj.borrow();
|
|
376
392
|
let entries: Vec<Value> = obj_borrow
|
|
@@ -407,6 +423,17 @@ pub fn object_assign(args: &[Value]) -> Value {
|
|
|
407
423
|
target_mut.strings.reserve(additional_capacity);
|
|
408
424
|
|
|
409
425
|
for source in args.iter().skip(1) {
|
|
426
|
+
// A boxed typed struct source materialises to its ordered object first (GBA/portable), so
|
|
427
|
+
// `Object.assign(t, s)` copies its fields instead of silently skipping it (see `object_keys`).
|
|
428
|
+
#[cfg(feature = "portable")]
|
|
429
|
+
let struct_obj;
|
|
430
|
+
#[cfg(feature = "portable")]
|
|
431
|
+
let source: &Value = if let Value::Struct(s) = source {
|
|
432
|
+
struct_obj = s.borrow().tish_to_object();
|
|
433
|
+
&struct_obj
|
|
434
|
+
} else {
|
|
435
|
+
source
|
|
436
|
+
};
|
|
410
437
|
if let Value::Object(src) = source {
|
|
411
438
|
let src_borrow = src.borrow();
|
|
412
439
|
for (k, v) in src_borrow.strings.iter() {
|