@tishlang/tish 1.0.29 → 1.0.34

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 (68) hide show
  1. package/Cargo.toml +1 -0
  2. package/crates/js_to_tish/src/transform/expr.rs +15 -6
  3. package/crates/tish/Cargo.toml +1 -1
  4. package/crates/tish/src/main.rs +1 -1
  5. package/crates/tish/tests/integration_test.rs +4 -3
  6. package/crates/tish_ast/src/ast.rs +65 -2
  7. package/crates/tish_build_utils/src/lib.rs +10 -2
  8. package/crates/tish_builtins/src/construct.rs +177 -0
  9. package/crates/tish_builtins/src/globals.rs +3 -5
  10. package/crates/tish_builtins/src/helpers.rs +2 -3
  11. package/crates/tish_builtins/src/lib.rs +1 -0
  12. package/crates/tish_builtins/src/object.rs +3 -4
  13. package/crates/tish_bytecode/src/compiler.rs +85 -11
  14. package/crates/tish_bytecode/src/opcode.rs +7 -3
  15. package/crates/tish_compile/Cargo.toml +1 -0
  16. package/crates/tish_compile/src/codegen.rs +604 -106
  17. package/crates/tish_compile/src/infer.rs +236 -0
  18. package/crates/tish_compile/src/lib.rs +52 -5
  19. package/crates/tish_compile/src/types.rs +42 -5
  20. package/crates/tish_compile_js/Cargo.toml +1 -0
  21. package/crates/tish_compile_js/src/codegen.rs +38 -94
  22. package/crates/tish_compile_js/src/lib.rs +0 -1
  23. package/crates/tish_compile_js/src/tests_jsx.rs +68 -0
  24. package/crates/tish_core/Cargo.toml +4 -0
  25. package/crates/tish_core/src/console_style.rs +7 -1
  26. package/crates/tish_core/src/json.rs +1 -2
  27. package/crates/tish_core/src/macros.rs +2 -3
  28. package/crates/tish_core/src/value.rs +13 -5
  29. package/crates/tish_cranelift/src/lib.rs +6 -4
  30. package/crates/tish_cranelift/src/lower.rs +5 -3
  31. package/crates/tish_cranelift_runtime/src/lib.rs +4 -2
  32. package/crates/tish_eval/Cargo.toml +2 -0
  33. package/crates/tish_eval/src/eval.rs +172 -79
  34. package/crates/tish_eval/src/http.rs +3 -4
  35. package/crates/tish_eval/src/lib.rs +7 -0
  36. package/crates/tish_eval/src/regex.rs +3 -2
  37. package/crates/tish_eval/src/value.rs +11 -13
  38. package/crates/tish_eval/src/value_convert.rs +4 -8
  39. package/crates/tish_fmt/src/lib.rs +49 -10
  40. package/crates/tish_lexer/src/token.rs +2 -0
  41. package/crates/tish_lint/src/lib.rs +9 -0
  42. package/crates/tish_llvm/src/lib.rs +4 -4
  43. package/crates/tish_lsp/README.md +1 -1
  44. package/crates/tish_native/src/build.rs +16 -2
  45. package/crates/tish_native/src/lib.rs +10 -11
  46. package/crates/tish_opt/src/lib.rs +15 -0
  47. package/crates/tish_parser/src/lib.rs +101 -1
  48. package/crates/tish_parser/src/parser.rs +168 -51
  49. package/crates/tish_runtime/src/http.rs +4 -5
  50. package/crates/tish_runtime/src/http_fetch.rs +17 -10
  51. package/crates/tish_runtime/src/lib.rs +9 -2
  52. package/crates/tish_runtime/src/promise.rs +2 -3
  53. package/crates/tish_runtime/src/promise_io.rs +2 -3
  54. package/crates/tish_runtime/src/ws.rs +7 -7
  55. package/crates/tish_ui/Cargo.toml +17 -0
  56. package/crates/tish_ui/src/jsx.rs +390 -0
  57. package/crates/tish_ui/src/lib.rs +16 -0
  58. package/crates/tish_ui/src/runtime/hooks.rs +122 -0
  59. package/crates/tish_ui/src/runtime/mod.rs +173 -0
  60. package/crates/tish_vm/src/vm.rs +121 -27
  61. package/justfile +3 -3
  62. package/package.json +1 -1
  63. package/platform/darwin-arm64/tish +0 -0
  64. package/platform/darwin-x64/tish +0 -0
  65. package/platform/linux-arm64/tish +0 -0
  66. package/platform/linux-x64/tish +0 -0
  67. package/platform/win32-x64/tish.exe +0 -0
  68. package/crates/tish_compile_js/src/js_intrinsics.rs +0 -82
@@ -3,12 +3,13 @@
3
3
  //! Re-exports core types from tishlang_core and provides interpreter-specific functionality.
4
4
 
5
5
  use std::cell::RefCell;
6
- use std::collections::HashMap;
7
6
  use std::rc::Rc;
8
7
  use std::sync::Arc;
9
8
 
10
9
  pub use tishlang_core::{RegExpFlags, TishRegExp};
11
10
 
11
+ use crate::value::PropMap;
12
+
12
13
  use crate::value::Value;
13
14
 
14
15
  /// RegExp.prototype.exec(string) - returns match object (array-like with index) or null
@@ -42,7 +43,7 @@ pub fn regexp_exec(re: &mut TishRegExp, input: &str) -> Value {
42
43
  let match_byte_start = byte_start + full_match.start();
43
44
  let match_char_index = input[..match_byte_start].chars().count();
44
45
 
45
- let mut obj: HashMap<Arc<str>, Value> = HashMap::new();
46
+ let mut obj: PropMap = PropMap::default();
46
47
  obj.insert(Arc::from("0"), Value::String(full_match.as_str().into()));
47
48
  for i in 1..caps.len() {
48
49
  let val = match caps.get(i) {
@@ -6,13 +6,15 @@
6
6
  //! different shape (no AST-carrying variants). The split is intentional.
7
7
 
8
8
  use std::cell::RefCell;
9
- use std::collections::HashMap;
10
9
  use std::rc::Rc;
11
10
  use std::sync::Arc;
12
11
 
13
- use tishlang_ast::{Expr, Statement};
14
- #[cfg(any(feature = "http", feature = "ws"))]
12
+ use ahash::AHashMap;
13
+ use tishlang_ast::{FunParam, Statement};
15
14
  use tishlang_core::NativeFn as CoreNativeFn;
15
+
16
+ /// Property map for interpreter `Value::Object` (uses `eval::Value`, not `tishlang_core::Value`).
17
+ pub type PropMap = AHashMap<Arc<str>, Value>;
16
18
  #[cfg(feature = "http")]
17
19
  use tishlang_core::TishPromise;
18
20
  use tishlang_core::TishOpaque;
@@ -32,11 +34,10 @@ pub enum Value {
32
34
  Bool(bool),
33
35
  Null,
34
36
  Array(Rc<RefCell<Vec<Value>>>),
35
- Object(Rc<RefCell<HashMap<Arc<str>, Value>>>),
37
+ Object(Rc<RefCell<PropMap>>),
36
38
  /// User-defined function with AST body
37
39
  Function {
38
- params: Arc<[Arc<str>]>,
39
- defaults: Arc<[Option<Expr>]>,
40
+ formals: Arc<[FunParam]>,
40
41
  rest_param: Option<Arc<str>>,
41
42
  body: Arc<Statement>,
42
43
  },
@@ -65,8 +66,7 @@ pub enum Value {
65
66
  /// Native `tishlang_core` Promise (fetch / reader.read / response.text).
66
67
  #[cfg(feature = "http")]
67
68
  CorePromise(Arc<dyn TishPromise>),
68
- /// `tishlang_core::Value::Function` (e.g. response.text/json, ws send/receive) callable from eval.
69
- #[cfg(any(feature = "http", feature = "ws"))]
69
+ /// `tishlang_core::Value::Function` (native callbacks, `new` constructors, fetch/ws when enabled).
70
70
  CoreFn(CoreNativeFn),
71
71
  /// Opaque handle to a native Rust type (e.g. Polars DataFrame).
72
72
  Opaque(Arc<dyn TishOpaque>),
@@ -99,7 +99,6 @@ impl std::fmt::Debug for Value {
99
99
  Value::BoundPromiseMethod(_, _) | Value::TimerBuiltin(_) => write!(f, "[Function]"),
100
100
  #[cfg(feature = "http")]
101
101
  Value::CorePromise(_) => write!(f, "Promise"),
102
- #[cfg(any(feature = "http", feature = "ws"))]
103
102
  Value::CoreFn(_) => write!(f, "CoreFn"),
104
103
  Value::Opaque(o) => write!(f, "{}(opaque)", o.type_name()),
105
104
  Value::OpaqueMethod(_, _) => write!(f, "[Function]"),
@@ -155,7 +154,6 @@ impl std::fmt::Display for Value {
155
154
  Value::BoundPromiseMethod(_, _) | Value::TimerBuiltin(_) => write!(f, "[Function]"),
156
155
  #[cfg(feature = "http")]
157
156
  Value::CorePromise(_) => write!(f, "[Promise]"),
158
- #[cfg(any(feature = "http", feature = "ws"))]
159
157
  Value::CoreFn(_) => write!(f, "[Function]"),
160
158
  Value::Opaque(o) => write!(f, "[object {}]", o.type_name()),
161
159
  Value::OpaqueMethod(_, _) => write!(f, "[Function]"),
@@ -199,8 +197,8 @@ impl Value {
199
197
  Value::Array(Rc::new(RefCell::new(items)))
200
198
  }
201
199
 
202
- /// Create a new object Value from a HashMap.
203
- pub fn object(map: HashMap<Arc<str>, Value>) -> Self {
200
+ /// Create a new object Value from a property map.
201
+ pub fn object(map: PropMap) -> Self {
204
202
  Value::Object(Rc::new(RefCell::new(map)))
205
203
  }
206
204
 
@@ -211,7 +209,7 @@ impl Value {
211
209
 
212
210
  /// Create an empty object Value.
213
211
  pub fn empty_object() -> Self {
214
- Value::Object(Rc::new(RefCell::new(HashMap::new())))
212
+ Value::Object(Rc::new(RefCell::new(PropMap::default())))
215
213
  }
216
214
 
217
215
  /// Extract the number value, if this is a Number.
@@ -1,13 +1,12 @@
1
1
  //! Conversion between tishlang_eval::Value and tishlang_core::Value for opaque method calls.
2
2
 
3
3
  use std::cell::RefCell;
4
- use std::collections::HashMap;
5
4
  use std::rc::Rc;
6
5
  use std::sync::Arc;
7
6
 
8
- use tishlang_core::Value as CoreValue;
7
+ use tishlang_core::{ObjectMap, Value as CoreValue};
9
8
 
10
- use crate::value::Value;
9
+ use crate::value::{PropMap, Value};
11
10
 
12
11
  /// Convert interpreter Value to core Value. Fails for interpreter-only variants.
13
12
  pub fn eval_to_core(v: &Value) -> Result<CoreValue, String> {
@@ -24,7 +23,7 @@ pub fn eval_to_core(v: &Value) -> Result<CoreValue, String> {
24
23
  Ok(CoreValue::Array(Rc::new(RefCell::new(out))))
25
24
  }
26
25
  Value::Object(map) => {
27
- let mut out = HashMap::new();
26
+ let mut out = ObjectMap::default();
28
27
  for (k, v) in map.borrow().iter() {
29
28
  out.insert(Arc::clone(k), eval_to_core(v)?);
30
29
  }
@@ -53,7 +52,7 @@ pub fn core_to_eval(v: CoreValue) -> Value {
53
52
  Value::Array(Rc::new(RefCell::new(out)))
54
53
  }
55
54
  CoreValue::Object(map) => {
56
- let mut out = HashMap::new();
55
+ let mut out = PropMap::default();
57
56
  for (k, v) in map.borrow().iter() {
58
57
  out.insert(Arc::clone(k), core_to_eval(v.clone()));
59
58
  }
@@ -64,10 +63,7 @@ pub fn core_to_eval(v: CoreValue) -> Value {
64
63
  CoreValue::Promise(p) => Value::CorePromise(Arc::clone(&p)),
65
64
  #[cfg(not(feature = "http"))]
66
65
  CoreValue::Promise(_) => Value::Null,
67
- #[cfg(any(feature = "http", feature = "ws"))]
68
66
  CoreValue::Function(f) => Value::CoreFn(Rc::clone(&f)),
69
- #[cfg(not(any(feature = "http", feature = "ws")))]
70
- CoreValue::Function(_) => Value::Null,
71
67
  // tishlang_core gets regex from http or regex features; handle RegExp when it exists
72
68
  #[cfg(any(feature = "http", feature = "regex"))]
73
69
  CoreValue::RegExp(re) => {
@@ -2,7 +2,7 @@
2
2
 
3
3
  use tishlang_ast::{
4
4
  ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern,
5
- ExportDeclaration, Expr, ImportSpecifier, JsxAttrValue, JsxChild, JsxProp,
5
+ ExportDeclaration, Expr, FunParam, ImportSpecifier, JsxAttrValue, JsxChild, JsxProp,
6
6
  Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Statement, TypeAnnotation,
7
7
  TypedParam, UnaryOp,
8
8
  };
@@ -385,19 +385,38 @@ impl Printer {
385
385
  self.buf.push_str(" }");
386
386
  }
387
387
 
388
- fn param_list(&mut self, params: &[TypedParam], rest: &Option<TypedParam>) {
388
+ fn param_list(&mut self, params: &[FunParam], rest: &Option<TypedParam>) {
389
389
  for (i, p) in params.iter().enumerate() {
390
390
  if i > 0 {
391
391
  self.buf.push_str(", ");
392
392
  }
393
- self.buf.push_str(p.name.as_ref());
394
- if let Some(t) = &p.type_ann {
395
- self.buf.push_str(": ");
396
- self.type_ann(t);
397
- }
398
- if let Some(e) = &p.default {
399
- self.buf.push_str(" = ");
400
- self.expr(e);
393
+ match p {
394
+ FunParam::Simple(tp) => {
395
+ self.buf.push_str(tp.name.as_ref());
396
+ if let Some(t) = &tp.type_ann {
397
+ self.buf.push_str(": ");
398
+ self.type_ann(t);
399
+ }
400
+ if let Some(e) = &tp.default {
401
+ self.buf.push_str(" = ");
402
+ self.expr(e);
403
+ }
404
+ }
405
+ FunParam::Destructure {
406
+ pattern,
407
+ type_ann,
408
+ default,
409
+ } => {
410
+ self.destruct_pat(pattern);
411
+ if let Some(t) = type_ann {
412
+ self.buf.push_str(": ");
413
+ self.type_ann(t);
414
+ }
415
+ if let Some(e) = default {
416
+ self.buf.push_str(" = ");
417
+ self.expr(e);
418
+ }
419
+ }
401
420
  }
402
421
  }
403
422
  if let Some(r) = rest {
@@ -551,6 +570,26 @@ impl Printer {
551
570
  }
552
571
  self.buf.push(')');
553
572
  }
573
+ Expr::New { callee, args, .. } => {
574
+ self.buf.push_str("new ");
575
+ self.expr(callee);
576
+ if !args.is_empty() {
577
+ self.buf.push('(');
578
+ for (i, a) in args.iter().enumerate() {
579
+ if i > 0 {
580
+ self.buf.push_str(", ");
581
+ }
582
+ match a {
583
+ CallArg::Expr(ex) => self.expr(ex),
584
+ CallArg::Spread(ex) => {
585
+ self.buf.push_str("...");
586
+ self.expr(ex);
587
+ }
588
+ }
589
+ }
590
+ self.buf.push(')');
591
+ }
592
+ }
554
593
  Expr::Member {
555
594
  object,
556
595
  prop,
@@ -54,6 +54,7 @@ pub enum TokenKind {
54
54
  In,
55
55
  Async,
56
56
  Await,
57
+ New,
57
58
  Import,
58
59
  Export,
59
60
 
@@ -149,6 +150,7 @@ impl TokenKind {
149
150
  "in" => TokenKind::In,
150
151
  "async" => TokenKind::Async,
151
152
  "await" => TokenKind::Await,
153
+ "new" => TokenKind::New,
152
154
  "import" => TokenKind::Import,
153
155
  "export" => TokenKind::Export,
154
156
  _ => TokenKind::Ident,
@@ -180,6 +180,15 @@ fn lint_expr(e: &Expr, out: &mut Vec<LintDiagnostic>) {
180
180
  }
181
181
  }
182
182
  }
183
+ Expr::New { callee, args, .. } => {
184
+ lint_expr(callee, out);
185
+ for a in args {
186
+ match a {
187
+ tishlang_ast::CallArg::Expr(x) => lint_expr(x, out),
188
+ tishlang_ast::CallArg::Spread(x) => lint_expr(x, out),
189
+ }
190
+ }
191
+ }
183
192
  Expr::Member { object, .. } => {
184
193
  lint_expr(object, out);
185
194
  }
@@ -1,8 +1,8 @@
1
- //! LLVM backend for Tish.
1
+ //! LLVM/clang link path for the **embedded-bytecode + VM** native binary.
2
2
  //!
3
- //! Compiles Tish bytecode to native binary using clang (LLVM toolchain).
4
- //! Emits a C file with embedded bytecode, compiles with clang to object code,
5
- //! then links with tishlang_cranelift_runtime.
3
+ //! Emits a C file that only holds the serialized chunk as bytes; clang produces `chunk.o`,
4
+ //! then links with **`tishlang_cranelift_runtime`** (same `tish_run_chunk` + `tishlang_vm`
5
+ //! entry as `--native-backend cranelift`). This is **not** LLVM IR lowering of Tish opcodes.
6
6
 
7
7
  use std::fs;
8
8
  use std::path::Path;
@@ -5,7 +5,7 @@ Language Server Protocol implementation for [Tish](https://github.com/tishlang/t
5
5
  ## Build
6
6
 
7
7
  ```bash
8
- cargo build --release -p tish_lsp
8
+ cargo build --release -p tishlang_lsp
9
9
  ```
10
10
 
11
11
  Binary: `target/release/tish-lsp` (stdio LSP).
@@ -45,6 +45,19 @@ pub fn build_via_cargo(
45
45
  })
46
46
  .collect();
47
47
 
48
+ let tish_ui_path = std::path::Path::new(&runtime_path)
49
+ .parent()
50
+ .ok_or_else(|| "invalid tishlang_runtime path (no parent)".to_string())?
51
+ .join("tish_ui");
52
+ let ui_dep = if rust_code.contains("tishlang_ui") {
53
+ format!(
54
+ "\ntishlang_ui = {{ path = {:?}, default-features = false, features = [\"runtime\"] }}\n",
55
+ tish_ui_path.display().to_string().replace('\\', "/")
56
+ )
57
+ } else {
58
+ String::new()
59
+ };
60
+
48
61
  let cargo_toml = format!(
49
62
  r#"[package]
50
63
  name = "tish_output"
@@ -63,7 +76,7 @@ codegen-units = 1
63
76
  lto = "thin"
64
77
 
65
78
  [dependencies]
66
- tishlang_runtime = {{ path = {:?}{} }}{}{}
79
+ tishlang_runtime = {{ path = {:?}{} }}{}{}{}
67
80
  "#,
68
81
  out_name,
69
82
  runtime_path,
@@ -73,7 +86,8 @@ tishlang_runtime = {{ path = {:?}{} }}{}{}
73
86
  String::new()
74
87
  } else {
75
88
  format!("\n{}", native_deps)
76
- }
89
+ },
90
+ ui_dep
77
91
  );
78
92
 
79
93
  fs::write(build_dir.join("Cargo.toml"), cargo_toml)
@@ -1,14 +1,13 @@
1
1
  //! Native code generation backend for Tish.
2
2
  //!
3
- //! Target architecture (per plan):
4
- //! - Phase 2: Bytecode -> Cranelift IR -> .o -> link with minimal runtime
5
- //! - Current: Delegates to tishlang_compile (Rust codegen) + cargo build as interim path
3
+ //! - **`rust`:** `tishlang_compile` emits Rust calling **`tishlang_runtime`** (`Value`, etc.),
4
+ //! then `cargo build --release` links the user binary.
5
+ //! - **`cranelift`:** Embeds serialized bytecode in an object file and links **`tishlang_cranelift_runtime`**
6
+ //! — the executable runs **`tishlang_vm`** on that chunk (same as `tish run --backend vm`), not CLIF lowering.
7
+ //! - **`llvm`:** Same embedded-bytecode + VM link path via `tishlang_llvm` / shared linker.
6
8
  //!
7
- //! Once Cranelift backend is implemented, this crate will:
8
- //! 1. Take Chunk (bytecode) as input
9
- //! 2. Lower to Cranelift IR
10
- //! 3. Emit .o via cranelift-object
11
- //! 4. Link against prebuilt tishlang_runtime staticlib
9
+ //! **Future:** Lower bytecode (or typed IR) through Cranelift/LLVM to real machine code where semantics allow;
10
+ //! emit Rust using `Vec<f64>` / fixed primitives instead of `Value` on hot paths.
12
11
 
13
12
  mod build;
14
13
 
@@ -31,9 +30,9 @@ impl std::error::Error for NativeError {}
31
30
 
32
31
  /// Compile a Tish project to a native binary.
33
32
  ///
34
- /// - `native_backend == "rust"`: Full Rust codegen + cargo build (supports native imports).
35
- /// - `native_backend == "cranelift"`: Bytecode -> Cranelift -> native (pure Tish only).
36
- /// - `native_backend == "llvm"`: Experimental LLVM backend (not implemented yet).
33
+ /// - `native_backend == "rust"`: Rust source + `tishlang_runtime` + cargo (native imports).
34
+ /// - `native_backend == "cranelift"`: Embedded bytecode + VM binary (pure Tish only); not opcode AOT yet.
35
+ /// - `native_backend == "llvm"`: Embedded bytecode + VM via LLVM/clang link path.
37
36
  pub fn compile_to_native(
38
37
  entry_path: &Path,
39
38
  project_root: Option<&Path>,
@@ -339,6 +339,21 @@ fn optimize_expr(expr: &Expr) -> Expr {
339
339
  .collect(),
340
340
  span: *span,
341
341
  },
342
+ Expr::New {
343
+ callee,
344
+ args,
345
+ span,
346
+ } => Expr::New {
347
+ callee: Box::new(optimize_expr(callee)),
348
+ args: args
349
+ .iter()
350
+ .map(|a| match a {
351
+ tishlang_ast::CallArg::Expr(e) => tishlang_ast::CallArg::Expr(optimize_expr(e)),
352
+ tishlang_ast::CallArg::Spread(e) => tishlang_ast::CallArg::Spread(optimize_expr(e)),
353
+ })
354
+ .collect(),
355
+ span: *span,
356
+ },
342
357
  Expr::Member {
343
358
  object,
344
359
  prop,
@@ -18,7 +18,7 @@ pub fn parse(source: &str) -> Result<Program, String> {
18
18
  #[cfg(test)]
19
19
  mod tests {
20
20
  use super::*;
21
- use tishlang_ast::{Expr, ObjectProp, Statement};
21
+ use tishlang_ast::{CallArg, Expr, ObjectProp, Statement};
22
22
 
23
23
  #[test]
24
24
  fn test_async_fn_parse() {
@@ -120,4 +120,104 @@ mod tests {
120
120
  _ => panic!("expected KeyValue prop"),
121
121
  }
122
122
  }
123
+
124
+ fn unwrap_expr_stmt(program: &tishlang_ast::Program) -> &Expr {
125
+ match program.statements.first() {
126
+ Some(Statement::ExprStmt { expr, .. }) => expr,
127
+ _ => panic!("expected expression statement"),
128
+ }
129
+ }
130
+
131
+ #[test]
132
+ fn new_expression_simple_call() {
133
+ let program = parse("new Foo()").expect("parse");
134
+ let e = unwrap_expr_stmt(&program);
135
+ match e {
136
+ Expr::New { callee, args, .. } => {
137
+ assert!(matches!(callee.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Foo"));
138
+ assert!(args.is_empty());
139
+ }
140
+ _ => panic!("expected New, got {:?}", e),
141
+ }
142
+ }
143
+
144
+ #[test]
145
+ fn new_expression_with_args() {
146
+ let program = parse("new Uint8Array(16)").expect("parse");
147
+ let e = unwrap_expr_stmt(&program);
148
+ match e {
149
+ Expr::New { callee, args, .. } => {
150
+ assert!(matches!(callee.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Uint8Array"));
151
+ assert_eq!(args.len(), 1);
152
+ assert!(matches!(&args[0], CallArg::Expr(Expr::Literal { .. })));
153
+ }
154
+ _ => panic!("expected New"),
155
+ }
156
+ }
157
+
158
+ #[test]
159
+ fn new_expression_member_callee() {
160
+ let program = parse("new ns.AudioContext()").expect("parse");
161
+ let e = unwrap_expr_stmt(&program);
162
+ match e {
163
+ Expr::New { callee, args, .. } => {
164
+ assert!(matches!(
165
+ callee.as_ref(),
166
+ Expr::Member { prop: tishlang_ast::MemberProp::Name(p), .. } if p.as_ref() == "AudioContext"
167
+ ));
168
+ assert!(args.is_empty());
169
+ }
170
+ _ => panic!("expected New"),
171
+ }
172
+ }
173
+
174
+ #[test]
175
+ fn new_expression_chained_new() {
176
+ let program = parse("new new Date()").expect("parse");
177
+ let e = unwrap_expr_stmt(&program);
178
+ match e {
179
+ Expr::New { callee, args, .. } => {
180
+ assert!(args.is_empty());
181
+ match callee.as_ref() {
182
+ Expr::New { callee: inner, args: inner_args, .. } => {
183
+ assert!(matches!(inner.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Date"));
184
+ assert!(inner_args.is_empty());
185
+ }
186
+ _ => panic!("expected nested New"),
187
+ }
188
+ }
189
+ _ => panic!("expected New"),
190
+ }
191
+ }
192
+
193
+ #[test]
194
+ fn new_then_member_access() {
195
+ let program = parse("new Foo().bar").expect("parse");
196
+ let e = unwrap_expr_stmt(&program);
197
+ match e {
198
+ Expr::Member { object, prop: tishlang_ast::MemberProp::Name(p), .. } => {
199
+ assert_eq!(p.as_ref(), "bar");
200
+ match object.as_ref() {
201
+ Expr::New { callee, args, .. } => {
202
+ assert!(matches!(callee.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Foo"));
203
+ assert!(args.is_empty());
204
+ }
205
+ _ => panic!("expected New object"),
206
+ }
207
+ }
208
+ _ => panic!("expected Member"),
209
+ }
210
+ }
211
+
212
+ #[test]
213
+ fn new_with_spread_arg() {
214
+ let program = parse("new Foo(...xs)").expect("parse");
215
+ let e = unwrap_expr_stmt(&program);
216
+ match e {
217
+ Expr::New { args, .. } => {
218
+ assert!(matches!(&args[0], CallArg::Spread(Expr::Ident { name, .. }) if name.as_ref() == "xs"));
219
+ }
220
+ _ => panic!("expected New"),
221
+ }
222
+ }
123
223
  }