@tishlang/tish 1.9.2 → 1.12.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.
Files changed (84) hide show
  1. package/bin/tish +0 -0
  2. package/crates/js_to_tish/src/transform/expr.rs +8 -6
  3. package/crates/js_to_tish/src/transform/stmt.rs +12 -13
  4. package/crates/tish/Cargo.toml +1 -1
  5. package/crates/tish/src/cargo_native_registry.rs +4 -1
  6. package/crates/tish/src/cli_help.rs +9 -1
  7. package/crates/tish/src/main.rs +66 -11
  8. package/crates/tish/tests/integration_test.rs +145 -7
  9. package/crates/tish_ast/src/ast.rs +3 -9
  10. package/crates/tish_build_utils/src/lib.rs +74 -23
  11. package/crates/tish_builtins/src/array.rs +2 -3
  12. package/crates/tish_builtins/src/construct.rs +15 -28
  13. package/crates/tish_builtins/src/globals.rs +18 -16
  14. package/crates/tish_builtins/src/helpers.rs +1 -4
  15. package/crates/tish_builtins/src/lib.rs +1 -0
  16. package/crates/tish_builtins/src/math.rs +7 -0
  17. package/crates/tish_builtins/src/object.rs +10 -10
  18. package/crates/tish_builtins/src/string.rs +27 -3
  19. package/crates/tish_builtins/src/symbol.rs +83 -0
  20. package/crates/tish_compile/src/codegen.rs +324 -158
  21. package/crates/tish_compile/src/lib.rs +39 -7
  22. package/crates/tish_compile/src/resolve.rs +191 -6
  23. package/crates/tish_compile/src/types.rs +6 -6
  24. package/crates/tish_compile_js/src/codegen.rs +8 -5
  25. package/crates/tish_core/src/console_style.rs +9 -0
  26. package/crates/tish_core/src/json.rs +17 -7
  27. package/crates/tish_core/src/macros.rs +2 -2
  28. package/crates/tish_core/src/value.rs +213 -4
  29. package/crates/tish_cranelift/src/link.rs +1 -1
  30. package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
  31. package/crates/tish_eval/src/eval.rs +135 -73
  32. package/crates/tish_eval/src/http.rs +18 -12
  33. package/crates/tish_eval/src/lib.rs +29 -0
  34. package/crates/tish_eval/src/regex.rs +1 -1
  35. package/crates/tish_eval/src/value.rs +89 -4
  36. package/crates/tish_eval/src/value_convert.rs +30 -8
  37. package/crates/tish_fmt/src/lib.rs +4 -1
  38. package/crates/tish_lexer/src/lib.rs +7 -2
  39. package/crates/tish_llvm/src/lib.rs +2 -2
  40. package/crates/tish_lsp/src/builtin_goto.rs +111 -10
  41. package/crates/tish_lsp/src/import_goto.rs +35 -22
  42. package/crates/tish_lsp/src/main.rs +118 -85
  43. package/crates/tish_native/src/build.rs +270 -24
  44. package/crates/tish_native/src/config.rs +48 -0
  45. package/crates/tish_native/src/lib.rs +139 -12
  46. package/crates/tish_parser/src/lib.rs +5 -2
  47. package/crates/tish_parser/src/parser.rs +45 -75
  48. package/crates/tish_pg/src/error.rs +1 -1
  49. package/crates/tish_pg/src/lib.rs +61 -73
  50. package/crates/tish_resolve/src/lib.rs +283 -158
  51. package/crates/tish_resolve/src/pos.rs +10 -2
  52. package/crates/tish_runtime/Cargo.toml +3 -0
  53. package/crates/tish_runtime/src/http.rs +39 -39
  54. package/crates/tish_runtime/src/http_fetch.rs +12 -12
  55. package/crates/tish_runtime/src/lib.rs +35 -44
  56. package/crates/tish_runtime/src/native_promise.rs +0 -11
  57. package/crates/tish_runtime/src/promise.rs +14 -1
  58. package/crates/tish_runtime/src/promise_io.rs +1 -4
  59. package/crates/tish_runtime/src/timers.rs +12 -7
  60. package/crates/tish_runtime/src/ws.rs +40 -27
  61. package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
  62. package/crates/tish_ui/src/jsx.rs +6 -4
  63. package/crates/tish_ui/src/lib.rs +5 -4
  64. package/crates/tish_ui/src/runtime/hooks.rs +123 -37
  65. package/crates/tish_ui/src/runtime/mod.rs +21 -41
  66. package/crates/tish_vm/Cargo.toml +2 -0
  67. package/crates/tish_vm/src/vm.rs +258 -153
  68. package/crates/tish_wasm/src/lib.rs +60 -7
  69. package/crates/tish_wasm_runtime/Cargo.toml +10 -1
  70. package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
  71. package/crates/tish_wasm_runtime/src/lib.rs +7 -1
  72. package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
  73. package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
  74. package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
  75. package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
  76. package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
  77. package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
  78. package/justfile +3 -3
  79. package/package.json +1 -1
  80. package/platform/darwin-arm64/tish +0 -0
  81. package/platform/darwin-x64/tish +0 -0
  82. package/platform/linux-arm64/tish +0 -0
  83. package/platform/linux-x64/tish +0 -0
  84. package/platform/win32-x64/tish.exe +0 -0
@@ -7,17 +7,27 @@ mod infer;
7
7
  mod resolve;
8
8
  mod types;
9
9
 
10
+ /// How generated Rust is linked (desktop binary vs embedded iOS staticlib).
11
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12
+ pub enum NativeEmitMode {
13
+ #[default]
14
+ DesktopBin,
15
+ /// `[lib] crate-type = ["staticlib"]` — no `fn main()`, host calls `tish_ios_launch`.
16
+ EmbeddedLib,
17
+ }
18
+
10
19
  pub use codegen::CompileError;
11
20
  pub use codegen::{
12
- compile, compile_project, compile_project_full, compile_with_features,
13
- compile_with_native_modules, compile_with_project_root,
21
+ compile, compile_project, compile_project_full, compile_project_full_emit,
22
+ compile_with_features, compile_with_native_modules, compile_with_native_modules_emit,
23
+ compile_with_project_root,
14
24
  };
15
25
  pub use resolve::{
16
- cargo_export_fn_name, compute_native_build_artifacts, detect_cycles, export_name_to_rust_ident,
17
- extract_native_import_features, format_rust_dependencies_toml, generate_native_wrapper_rs,
18
- has_external_native_imports, has_native_imports, infer_native_module_exports,
19
- is_builtin_native_spec, is_cargo_native_spec, is_native_import, merge_modules,
20
- normalize_builtin_spec, read_project_tish_config,
26
+ cargo_export_fn_name, compute_native_build_artifacts, detect_cycles, ensure_tish_canvas_module,
27
+ export_name_to_rust_ident, extract_native_import_features, format_rust_dependencies_toml,
28
+ generate_native_wrapper_rs, has_external_native_imports, has_native_imports,
29
+ infer_native_module_exports, is_builtin_native_spec, is_cargo_native_spec, is_native_import,
30
+ merge_modules, normalize_builtin_spec, program_uses_document, read_project_tish_config,
21
31
  resolve_bare_spec, resolve_native_modules, resolve_project, resolve_project_from_stdin,
22
32
  MergedProgram, NativeBuildArtifacts, NativeModuleInit, ResolvedNativeModule,
23
33
  };
@@ -106,6 +116,28 @@ fn factory() {
106
116
  );
107
117
  }
108
118
 
119
+ /// `value_call` must take `&Value` to a **local** (`let _callee = (<expr>).clone(); … &_callee`):
120
+ /// `&<temporary>` can dangle in release, and `let _callee = <ident>` would move globals like `Symbol`.
121
+ #[test]
122
+ fn native_emit_value_call_materializes_callee() {
123
+ use std::path::PathBuf;
124
+ let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
125
+ let path = manifest.join("../../tests/core/symbol.tish").canonicalize().unwrap();
126
+ let (rust, _, _, _) = compile_project_full(&path, path.parent(), &[], true).unwrap();
127
+ assert!(
128
+ rust.contains("let _callee = (tishlang_runtime::get_index"),
129
+ "fixture should bracket-call via get_index with callee stored in a local"
130
+ );
131
+ assert!(
132
+ !rust.contains("let _callee = &tishlang_runtime::get_index"),
133
+ "expected callee materialization, found reference-to-temporary pattern"
134
+ );
135
+ assert!(
136
+ rust.contains("tishlang_runtime::value_call"),
137
+ "expected value_call via runtime re-export for nested Cargo builds"
138
+ );
139
+ }
140
+
109
141
  #[test]
110
142
  fn loop_var_decl_clone_via_project_full() {
111
143
  // With the inference pass, `let outerVar = 42` is inferred as f64 (Copy) — no clone needed.
@@ -4,7 +4,7 @@
4
4
  use std::collections::{HashMap, HashSet};
5
5
  use std::path::{Path, PathBuf};
6
6
  use std::sync::Arc;
7
- use tishlang_ast::{ExportDeclaration, Expr, ImportSpecifier, Program, Statement};
7
+ use tishlang_ast::{ExportDeclaration, Expr, ImportSpecifier, MemberProp, Program, Statement, CallArg};
8
8
 
9
9
  /// Resolved native module: crate path and init expression.
10
10
  #[derive(Debug, Clone)]
@@ -67,8 +67,10 @@ pub fn normalize_builtin_spec(spec: &str) -> Option<String> {
67
67
 
68
68
  /// Built-in modules that come from tishlang_runtime, not from package.json.
69
69
  pub fn is_builtin_native_spec(spec: &str) -> bool {
70
- matches!(spec, "tish:fs" | "tish:http" | "tish:timers" | "tish:process" | "tish:ws")
71
- || matches!(spec, "fs" | "http" | "timers" | "process" | "ws")
70
+ matches!(
71
+ spec,
72
+ "tish:fs" | "tish:http" | "tish:timers" | "tish:process" | "tish:ws"
73
+ ) || matches!(spec, "fs" | "http" | "timers" | "process" | "ws")
72
74
  }
73
75
 
74
76
  /// Resolve all native imports in a merged program via package.json lookup.
@@ -112,6 +114,188 @@ pub fn resolve_native_modules(
112
114
  Ok(modules)
113
115
  }
114
116
 
117
+ /// True when merged Tish source references the browser global `document` (e.g. juke-cards).
118
+ pub fn program_uses_document(program: &Program) -> bool {
119
+ use tishlang_ast::{ArrayElement, ArrowBody, JsxAttrValue, JsxChild, JsxProp, ObjectProp};
120
+
121
+ fn expr_uses_document(e: &Expr) -> bool {
122
+ match e {
123
+ Expr::Ident { name, .. } => name.as_ref() == "document",
124
+ Expr::Literal { .. } | Expr::NativeModuleLoad { .. } => false,
125
+ Expr::Binary { left, right, .. } => {
126
+ expr_uses_document(left) || expr_uses_document(right)
127
+ }
128
+ Expr::Unary { operand, .. } | Expr::TypeOf { operand, .. } => {
129
+ expr_uses_document(operand)
130
+ }
131
+ Expr::Call { callee, args, .. } => {
132
+ expr_uses_document(callee)
133
+ || args.iter().any(|a| match a {
134
+ CallArg::Expr(e) | CallArg::Spread(e) => expr_uses_document(e),
135
+ })
136
+ }
137
+ Expr::New { callee, args, .. } => {
138
+ expr_uses_document(callee)
139
+ || args.iter().any(|a| match a {
140
+ CallArg::Expr(e) | CallArg::Spread(e) => expr_uses_document(e),
141
+ })
142
+ }
143
+ Expr::Member { object, prop, .. } => {
144
+ expr_uses_document(object)
145
+ || if let MemberProp::Expr(e) = prop {
146
+ expr_uses_document(e)
147
+ } else {
148
+ false
149
+ }
150
+ }
151
+ Expr::Index { object, index, .. } => {
152
+ expr_uses_document(object) || expr_uses_document(index)
153
+ }
154
+ Expr::Conditional {
155
+ cond,
156
+ then_branch,
157
+ else_branch,
158
+ ..
159
+ } => {
160
+ expr_uses_document(cond)
161
+ || expr_uses_document(then_branch)
162
+ || expr_uses_document(else_branch)
163
+ }
164
+ Expr::NullishCoalesce { left, right, .. } => {
165
+ expr_uses_document(left) || expr_uses_document(right)
166
+ }
167
+ Expr::Array { elements, .. } => elements.iter().any(|el| match el {
168
+ ArrayElement::Expr(e) | ArrayElement::Spread(e) => expr_uses_document(e),
169
+ }),
170
+ Expr::Object { props, .. } => props.iter().any(|p| match p {
171
+ ObjectProp::KeyValue(_, e) | ObjectProp::Spread(e) => expr_uses_document(e),
172
+ }),
173
+ Expr::Assign { value, .. }
174
+ | Expr::CompoundAssign { value, .. }
175
+ | Expr::LogicalAssign { value, .. }
176
+ | Expr::MemberAssign { value, .. }
177
+ | Expr::IndexAssign { value, .. } => expr_uses_document(value),
178
+ Expr::PostfixInc { .. }
179
+ | Expr::PostfixDec { .. }
180
+ | Expr::PrefixInc { .. }
181
+ | Expr::PrefixDec { .. } => false,
182
+ Expr::ArrowFunction { body, .. } => match body {
183
+ ArrowBody::Expr(e) => expr_uses_document(e),
184
+ ArrowBody::Block(s) => stmt_uses_document(s),
185
+ },
186
+ Expr::TemplateLiteral { exprs, .. } => exprs.iter().any(expr_uses_document),
187
+ Expr::Await { operand, .. } => expr_uses_document(operand),
188
+ Expr::JsxElement { props, children, .. } => {
189
+ props.iter().any(|p| match p {
190
+ JsxProp::Attr { value, .. } => match value {
191
+ JsxAttrValue::Expr(e) => expr_uses_document(e),
192
+ JsxAttrValue::String(_) | JsxAttrValue::ImplicitTrue => false,
193
+ },
194
+ JsxProp::Spread(e) => expr_uses_document(e),
195
+ }) || children.iter().any(|c| match c {
196
+ JsxChild::Expr(e) => expr_uses_document(e),
197
+ JsxChild::Text(_) => false,
198
+ })
199
+ }
200
+ Expr::JsxFragment { children, .. } => children.iter().any(|c| match c {
201
+ JsxChild::Expr(e) => expr_uses_document(e),
202
+ JsxChild::Text(_) => false,
203
+ }),
204
+ }
205
+ }
206
+
207
+ fn stmt_uses_document(s: &Statement) -> bool {
208
+ match s {
209
+ Statement::VarDecl { init, .. } => init.as_ref().is_some_and(|e| expr_uses_document(e)),
210
+ Statement::VarDeclDestructure { init, .. } => expr_uses_document(init),
211
+ Statement::ExprStmt { expr, .. } => expr_uses_document(expr),
212
+ Statement::Return { value, .. } => value.as_ref().is_some_and(|e| expr_uses_document(e)),
213
+ Statement::Throw { value, .. } => expr_uses_document(value),
214
+ Statement::If {
215
+ cond,
216
+ then_branch,
217
+ else_branch,
218
+ ..
219
+ } => {
220
+ expr_uses_document(cond)
221
+ || stmt_uses_document(then_branch)
222
+ || else_branch
223
+ .as_ref()
224
+ .is_some_and(|b| stmt_uses_document(b.as_ref()))
225
+ }
226
+ Statement::While { cond, body, .. }
227
+ | Statement::DoWhile { cond, body, .. } => {
228
+ expr_uses_document(cond) || stmt_uses_document(body)
229
+ }
230
+ Statement::For { init, cond, update, body, .. } => {
231
+ init.as_ref().is_some_and(|s| stmt_uses_document(s.as_ref()))
232
+ || cond.as_ref().is_some_and(|e| expr_uses_document(e))
233
+ || update.as_ref().is_some_and(|e| expr_uses_document(e))
234
+ || stmt_uses_document(body)
235
+ }
236
+ Statement::ForOf { iterable, body, .. } => {
237
+ expr_uses_document(iterable) || stmt_uses_document(body)
238
+ }
239
+ Statement::Switch {
240
+ expr,
241
+ cases,
242
+ default_body,
243
+ ..
244
+ } => {
245
+ expr_uses_document(expr)
246
+ || cases.iter().any(|(e, stmts)| {
247
+ e.as_ref().is_some_and(|e| expr_uses_document(e))
248
+ || stmts.iter().any(stmt_uses_document)
249
+ })
250
+ || default_body
251
+ .as_ref()
252
+ .is_some_and(|stmts| stmts.iter().any(stmt_uses_document))
253
+ }
254
+ Statement::Block { statements, .. } => statements.iter().any(stmt_uses_document),
255
+ Statement::FunDecl { body, .. } => stmt_uses_document(body),
256
+ Statement::Try {
257
+ body,
258
+ catch_body,
259
+ finally_body,
260
+ ..
261
+ } => {
262
+ stmt_uses_document(body)
263
+ || catch_body
264
+ .as_ref()
265
+ .is_some_and(|b| stmt_uses_document(b.as_ref()))
266
+ || finally_body
267
+ .as_ref()
268
+ .is_some_and(|b| stmt_uses_document(b.as_ref()))
269
+ }
270
+ Statement::Import { .. }
271
+ | Statement::Export { .. }
272
+ | Statement::Break { .. }
273
+ | Statement::Continue { .. }
274
+ | Statement::TypeAlias { .. }
275
+ | Statement::DeclareVar { .. }
276
+ | Statement::DeclareFun { .. } => false,
277
+ }
278
+ }
279
+
280
+ program.statements.iter().any(stmt_uses_document)
281
+ }
282
+
283
+ /// When Tish uses bare `document`, link `tish-canvas` even without `import from 'tish:canvas'`.
284
+ pub fn ensure_tish_canvas_module(
285
+ native_modules: &mut Vec<ResolvedNativeModule>,
286
+ project_root: &Path,
287
+ ) -> Result<(), String> {
288
+ if native_modules
289
+ .iter()
290
+ .any(|m| m.crate_name == "tish_canvas" || m.package_name == "tish-canvas")
291
+ {
292
+ return Ok(());
293
+ }
294
+ let m = resolve_native_module("tish:canvas", project_root)?;
295
+ native_modules.push(m);
296
+ Ok(())
297
+ }
298
+
115
299
  /// True for `cargo:…` specs (Cargo-backed imports; Rust native backend only).
116
300
  pub fn is_cargo_native_spec(spec: &str) -> bool {
117
301
  spec.starts_with("cargo:")
@@ -723,11 +907,11 @@ fn load_module_recursive(
723
907
  /// - fs, http, timers, process, ws (Node-compatible aliases for tish:*)
724
908
  /// - tish:egui, tish:polars, etc.
725
909
  /// - cargo:… (Cargo `rustDependencies` + generated wrapper; Rust native backend)
726
- /// - @scope/package (npm-style)
910
+ ///
911
+ /// Scoped npm packages (`@scope/pkg`) are merged as Tish source unless imported via `tish:…`.
727
912
  pub fn is_native_import(spec: &str) -> bool {
728
913
  spec.starts_with("tish:")
729
914
  || spec.starts_with("cargo:")
730
- || spec.starts_with('@')
731
915
  || matches!(spec, "fs" | "http" | "timers" | "process" | "ws")
732
916
  }
733
917
 
@@ -774,7 +958,8 @@ fn resolve_package_root_to_entry(pkg_root: &Path, spec: &str) -> Option<PathBuf>
774
958
  pub fn resolve_bare_spec(spec: &str, from_dir: &Path, _project_root: &Path) -> Option<PathBuf> {
775
959
  let mut search = from_dir.to_path_buf();
776
960
  loop {
777
- if let Some(p) = resolve_package_root_to_entry(&search.join("node_modules").join(spec), spec)
961
+ if let Some(p) =
962
+ resolve_package_root_to_entry(&search.join("node_modules").join(spec), spec)
778
963
  {
779
964
  return Some(p);
780
965
  }
@@ -83,9 +83,9 @@ impl RustType {
83
83
  RustType::Value
84
84
  }
85
85
  },
86
- TypeAnnotation::Array(elem) => RustType::Vec(Box::new(
87
- Self::from_annotation_with_aliases(elem, aliases),
88
- )),
86
+ TypeAnnotation::Array(elem) => {
87
+ RustType::Vec(Box::new(Self::from_annotation_with_aliases(elem, aliases)))
88
+ }
89
89
  TypeAnnotation::Object(fields) => {
90
90
  let typed_fields: Vec<_> = fields
91
91
  .iter()
@@ -115,9 +115,9 @@ impl RustType {
115
115
  |t| !matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null"),
116
116
  );
117
117
  if let Some(inner) = non_null {
118
- return RustType::Option(Box::new(
119
- Self::from_annotation_with_aliases(inner, aliases),
120
- ));
118
+ return RustType::Option(Box::new(Self::from_annotation_with_aliases(
119
+ inner, aliases,
120
+ )));
121
121
  }
122
122
  }
123
123
  }
@@ -72,7 +72,11 @@ impl Codegen {
72
72
  }
73
73
 
74
74
  fn output_line(&self) -> u32 {
75
- self.output.as_bytes().iter().filter(|&&b| b == b'\n').count() as u32
75
+ self.output
76
+ .as_bytes()
77
+ .iter()
78
+ .filter(|&&b| b == b'\n')
79
+ .count() as u32
76
80
  }
77
81
 
78
82
  fn escape_ident(s: &str) -> String {
@@ -779,7 +783,8 @@ fn compile_project_js_inner(
779
783
  let modules = tishlang_compile::resolve_project(entry_path, project_root)
780
784
  .map_err(|e| CompileError { message: e })?;
781
785
  tishlang_compile::detect_cycles(&modules).map_err(|e| CompileError { message: e })?;
782
- let merged = tishlang_compile::merge_modules(modules).map_err(|e| CompileError { message: e })?;
786
+ let merged =
787
+ tishlang_compile::merge_modules(modules).map_err(|e| CompileError { message: e })?;
783
788
  let program = if optimize {
784
789
  tishlang_opt::optimize(&merged.program)
785
790
  } else {
@@ -862,7 +867,5 @@ pub fn compile_project_with_jsx(
862
867
  } else {
863
868
  format!("{stem}.js")
864
869
  };
865
- Ok(
866
- compile_project_js_inner(entry_path, project_root, optimize, false, &out_name)?.js,
867
- )
870
+ Ok(compile_project_js_inner(entry_path, project_root, optimize, false, &out_name)?.js)
868
871
  }
@@ -84,6 +84,7 @@ fn format_value_styled_inner(value: &Value, colors: bool, quote_strings: bool) -
84
84
  Value::Object(obj) => {
85
85
  let inner: Vec<String> = obj
86
86
  .borrow()
87
+ .strings
87
88
  .iter()
88
89
  .map(|(k, v)| {
89
90
  format!(
@@ -96,6 +97,14 @@ fn format_value_styled_inner(value: &Value, colors: bool, quote_strings: bool) -
96
97
  let sep = format!("{PUNCT}, {RESET}");
97
98
  format!("{PUNCT}{{{RESET} {} {PUNCT}}}{RESET}", inner.join(&sep))
98
99
  }
100
+ Value::Symbol(s) => {
101
+ let body = s
102
+ .description
103
+ .as_ref()
104
+ .map(|d| d.as_ref())
105
+ .unwrap_or("");
106
+ format!("{SPECIAL}Symbol({body}){RESET}")
107
+ }
99
108
  Value::Function(_) => format!("{SPECIAL}[Function]{RESET}"),
100
109
  Value::Promise(_) => format!("{SPECIAL}[object Promise]{RESET}"),
101
110
  Value::Opaque(o) => format!("{SPECIAL}[object {}]{RESET}", o.type_name()),
@@ -70,8 +70,8 @@ pub fn json_stringify_into(buf: &mut String, value: &Value) {
70
70
  let borrowed = obj.borrow();
71
71
  // Sort keys for deterministic output. Pre-allocate to avoid
72
72
  // a fresh `Vec` realloc inside `keys().collect()`.
73
- let mut keys: Vec<&Arc<str>> = Vec::with_capacity(borrowed.len());
74
- keys.extend(borrowed.keys());
73
+ let mut keys: Vec<&Arc<str>> = Vec::with_capacity(borrowed.strings.len());
74
+ keys.extend(borrowed.strings.keys());
75
75
  keys.sort_unstable_by(|a, b| a.as_ref().cmp(b.as_ref()));
76
76
  buf.push('{');
77
77
  for (i, key) in keys.into_iter().enumerate() {
@@ -81,11 +81,13 @@ pub fn json_stringify_into(buf: &mut String, value: &Value) {
81
81
  buf.push('"');
82
82
  escape_json_string_into(buf, key);
83
83
  buf.push_str("\":");
84
- json_stringify_into(buf, borrowed.get(key).unwrap());
84
+ json_stringify_into(buf, borrowed.strings.get(key).unwrap());
85
85
  }
86
86
  buf.push('}');
87
87
  }
88
- Value::Function(_) | Value::Promise(_) | Value::Opaque(_) => buf.push_str("null"),
88
+ Value::Function(_) | Value::Promise(_) | Value::Opaque(_) | Value::Symbol(_) => {
89
+ buf.push_str("null");
90
+ }
89
91
  #[cfg(feature = "regex")]
90
92
  Value::RegExp(_) => buf.push_str("null"),
91
93
  }
@@ -312,7 +314,10 @@ fn parse_object(input: &str) -> Result<(Value, &str), String> {
312
314
 
313
315
  input = input.trim_start();
314
316
  if let Some(rest) = input.strip_prefix('}') {
315
- return Ok((Value::Object(VmRef::new(map)), rest));
317
+ return Ok((
318
+ Value::Object(VmRef::new(crate::ObjectData::from_strings(map))),
319
+ rest,
320
+ ));
316
321
  }
317
322
 
318
323
  loop {
@@ -339,7 +344,12 @@ fn parse_object(input: &str) -> Result<(Value, &str), String> {
339
344
 
340
345
  match input.chars().next() {
341
346
  Some(',') => input = &input[1..],
342
- Some('}') => return Ok((Value::Object(VmRef::new(map)), &input[1..])),
347
+ Some('}') => {
348
+ return Ok((
349
+ Value::Object(VmRef::new(crate::ObjectData::from_strings(map))),
350
+ &input[1..],
351
+ ));
352
+ }
343
353
  _ => return Err("Expected ',' or '}' in object".to_string()),
344
354
  }
345
355
  }
@@ -369,7 +379,7 @@ mod tests {
369
379
 
370
380
  match (&value, &reparsed) {
371
381
  (Value::Object(a), Value::Object(b)) => {
372
- assert_eq!(a.borrow().len(), b.borrow().len());
382
+ assert_eq!(a.borrow().len_entries(), b.borrow().len_entries());
373
383
  }
374
384
  _ => panic!("Expected objects"),
375
385
  }
@@ -24,13 +24,13 @@
24
24
  macro_rules! tish_module {
25
25
  ($($name:expr => $fn:expr),* $(,)?) => {{
26
26
  use std::sync::Arc;
27
- use $crate::{ObjectMap, Value, VmRef};
27
+ use $crate::{ObjectMap, Value};
28
28
  let mut map = ObjectMap::default();
29
29
  $(
30
30
  // `Value::native` picks the right Rc / Arc wrapper depending on
31
31
  // whether the `send-values` feature is enabled upstream.
32
32
  map.insert(Arc::from($name), Value::native($fn));
33
33
  )*
34
- Value::Object(VmRef::new(map))
34
+ Value::object(map)
35
35
  }};
36
36
  }