@tishlang/tish 1.3.8 → 1.5.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 (34) hide show
  1. package/bin/tish +0 -0
  2. package/crates/tish/Cargo.toml +2 -2
  3. package/crates/tish/src/cli_help.rs +504 -0
  4. package/crates/tish/src/main.rs +76 -90
  5. package/crates/tish/src/repl_completion.rs +1 -1
  6. package/crates/tish/tests/integration_test.rs +48 -0
  7. package/crates/tish_build_utils/src/lib.rs +171 -1
  8. package/crates/tish_builtins/src/string.rs +248 -0
  9. package/crates/tish_bytecode/Cargo.toml +1 -0
  10. package/crates/tish_bytecode/src/compiler.rs +289 -66
  11. package/crates/tish_bytecode/src/opcode.rs +13 -3
  12. package/crates/tish_bytecode/src/peephole.rs +21 -16
  13. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  14. package/crates/tish_compile/src/codegen.rs +214 -79
  15. package/crates/tish_compile/src/lib.rs +1 -1
  16. package/crates/tish_core/src/value.rs +1 -0
  17. package/crates/tish_eval/src/eval.rs +39 -1
  18. package/crates/tish_eval/src/lib.rs +1 -1
  19. package/crates/tish_lint/Cargo.toml +1 -0
  20. package/crates/tish_lint/src/bin/tish-lint.rs +141 -23
  21. package/crates/tish_lint/src/lib.rs +3 -1
  22. package/crates/tish_lsp/README.md +1 -1
  23. package/crates/tish_native/src/build.rs +48 -7
  24. package/crates/tish_native/src/lib.rs +8 -3
  25. package/crates/tish_runtime/src/lib.rs +4 -0
  26. package/crates/tish_vm/src/lib.rs +1 -1
  27. package/crates/tish_vm/src/vm.rs +155 -16
  28. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  29. package/package.json +1 -8
  30. package/platform/darwin-arm64/tish +0 -0
  31. package/platform/darwin-x64/tish +0 -0
  32. package/platform/linux-arm64/tish +0 -0
  33. package/platform/linux-x64/tish +0 -0
  34. package/platform/win32-x64/tish.exe +0 -0
@@ -1,80 +1,59 @@
1
1
  //! Tish CLI - run, REPL, build to native or other targets.
2
2
 
3
+ mod cli_help;
3
4
  mod repl_completion;
4
5
 
5
6
  use std::cell::RefCell;
7
+ use std::collections::HashSet;
6
8
  use std::fs;
7
9
  use std::io::{self, IsTerminal, Read, Write};
8
10
  use std::path::{Path, PathBuf};
9
11
  use std::rc::Rc;
10
12
 
11
- use clap::{Parser, Subcommand};
13
+ use clap::FromArgMatches;
12
14
  use rustyline::{Behavior, ColorMode, CompletionType, Config, Editor};
13
15
 
14
- #[derive(Parser)]
15
- #[command(name = "tish")]
16
- #[command(about = "Tish - minimal TS/JS-compatible language")]
17
- #[command(version = env!("CARGO_PKG_VERSION"))]
18
- #[command(after_help = "To disable optimizations: TISH_NO_OPTIMIZE=1")]
19
- pub(crate) struct Cli {
20
- #[command(subcommand)]
21
- command: Option<Commands>,
22
- }
23
-
24
- #[derive(Parser)]
25
- struct RunArgs {
26
- /// Path to a `.tish` file, or `-` to read the program from stdin (like `node -`).
27
- #[arg(required = true, allow_hyphen_values = true)]
28
- file: String,
29
- #[arg(long, default_value = "vm")]
30
- backend: String,
31
- /// Enable capabilities (http, fs, process, regex, ws). Must match how tish was built.
32
- /// E.g. cargo run -p tishlang--features http,fs -- run script.tish --feature http,fs
33
- #[arg(long = "feature", action = clap::ArgAction::Append)]
34
- features: Vec<String>,
35
- /// Disable AST and bytecode optimizations (for debugging)
36
- #[arg(long)]
37
- no_optimize: bool,
38
- }
16
+ use cli_help::{Cli, Commands};
39
17
 
40
- #[derive(Parser)]
41
- struct ReplArgs {
42
- #[arg(long, default_value = "vm")]
43
- backend: String,
44
- #[arg(long)]
45
- no_optimize: bool,
18
+ /// Normalize `--feature` / `--feature http,fs` / `--feature full` for VM runs and native builds.
19
+ fn normalize_capability_flags(features: &[String]) -> HashSet<String> {
20
+ let mut out = HashSet::new();
21
+ for s in features {
22
+ for part in s.split(',').map(str::trim).filter(|p| !p.is_empty()) {
23
+ if part == "full" {
24
+ for name in ["http", "fs", "process", "regex", "ws"] {
25
+ out.insert(name.to_string());
26
+ }
27
+ } else {
28
+ out.insert(part.to_string());
29
+ }
30
+ }
31
+ }
32
+ out
46
33
  }
47
34
 
48
- #[derive(Parser)]
49
- struct BuildArgs {
50
- #[arg(short, long, default_value = "tish_out")]
51
- output: String,
52
- #[arg(long, default_value = "native")]
53
- target: String,
54
- #[arg(long, default_value = "rust")]
55
- native_backend: String,
56
- #[arg(long = "feature", action = clap::ArgAction::Append)]
57
- features: Vec<String>,
58
- #[arg(long)]
59
- no_optimize: bool,
60
- #[arg(required = true)]
61
- file: String,
35
+ /// VM capabilities for `run` / `repl` / stdin with the bytecode VM.
36
+ ///
37
+ /// If the user passes no `--feature`, enable **everything linked into this `tish` binary**
38
+ /// (so `cargo run --bin tish --features full -- script.tish` does not need `--feature full`).
39
+ /// If they pass `--feature …`, use **only** that set (e.g. restrict a full build to `http` only).
40
+ fn vm_capabilities_for_cli_run(cli_features: &[String]) -> HashSet<String> {
41
+ if cli_features.is_empty() {
42
+ tishlang_vm::all_compiled_capabilities()
43
+ } else {
44
+ normalize_capability_flags(cli_features)
45
+ }
62
46
  }
63
47
 
64
- #[derive(Subcommand)]
65
- pub(crate) enum Commands {
66
- /// Run a Tish file (interpret)
67
- Run(RunArgs),
68
- /// Interactive REPL
69
- Repl(ReplArgs),
70
- /// Build native binary, wasm, wasi, or JavaScript output
71
- Build(BuildArgs),
72
- /// Parse and dump AST
73
- #[command(name = "dump-ast")]
74
- DumpAst {
75
- #[arg(required = true)]
76
- file: String,
77
- },
48
+ /// `--feature` list for `tish build --target native`: same default as `tish run` (all linked-in caps).
49
+ fn native_build_features_from_cli(cli_features: &[String]) -> Vec<String> {
50
+ if cli_features.is_empty() {
51
+ let mut v: Vec<String> = tishlang_vm::all_compiled_capabilities().into_iter().collect();
52
+ v.sort();
53
+ v
54
+ } else {
55
+ cli_features.to_vec()
56
+ }
78
57
  }
79
58
 
80
59
  /// `tish script.tish` → insert `run` so it matches `tish run script.tish` (npx / npm UX).
@@ -107,11 +86,17 @@ fn main() {
107
86
  return;
108
87
  }
109
88
 
89
+ if cli_help::argv_requests_help(&argv) {
90
+ cli_help::print_banner_with_help(&argv);
91
+ std::process::exit(0);
92
+ }
93
+
110
94
  let argv = argv_with_implicit_run(argv);
111
- let cli = Cli::parse_from(argv);
95
+ let matches = cli_help::build_command().get_matches_from(&argv);
96
+ let cli = Cli::from_arg_matches(&matches).unwrap_or_else(|e| e.exit());
112
97
  let result = match cli.command {
113
98
  Some(Commands::Run(a)) => run_file(&a.file, &a.backend, &a.features, a.no_optimize || no_opt_env),
114
- Some(Commands::Repl(a)) => run_repl(&a.backend, a.no_optimize || no_opt_env),
99
+ Some(Commands::Repl(a)) => run_repl(&a.backend, a.no_optimize || no_opt_env, &a.features),
115
100
  Some(Commands::Build(a)) => build_file(
116
101
  &a.file,
117
102
  &a.output,
@@ -123,7 +108,7 @@ fn main() {
123
108
  Some(Commands::DumpAst { file }) => dump_ast(&file),
124
109
  None => {
125
110
  if io::stdin().is_terminal() {
126
- run_repl("vm", no_opt_env)
111
+ run_repl("vm", no_opt_env, &[])
127
112
  } else {
128
113
  // `echo '...' | tish` — run script from stdin (Bun-style)
129
114
  run_stdin_pipe("vm", &[], no_opt_env, false)
@@ -162,7 +147,7 @@ fn run_stdin_pipe(
162
147
  fn run_stdin_source(
163
148
  source: &str,
164
149
  backend: &str,
165
- _features: &[String],
150
+ features: &[String],
166
151
  no_optimize: bool,
167
152
  ) -> Result<(), String> {
168
153
  let cwd = std::env::current_dir().map_err(|e| e.to_string())?;
@@ -174,7 +159,7 @@ fn run_stdin_source(
174
159
  } else {
175
160
  tishlang_opt::optimize(&prog)
176
161
  };
177
- run_program(&program, backend, no_optimize)
162
+ run_program(&program, backend, no_optimize, features)
178
163
  }
179
164
 
180
165
  fn run_file(path: &str, backend: &str, features: &[String], no_optimize: bool) -> Result<(), String> {
@@ -211,10 +196,15 @@ fn run_file(path: &str, backend: &str, features: &[String], no_optimize: bool) -
211
196
  }
212
197
  };
213
198
 
214
- run_program(&program, backend, no_optimize)
199
+ run_program(&program, backend, no_optimize, features)
215
200
  }
216
201
 
217
- fn run_program(program: &tishlang_ast::Program, backend: &str, no_optimize: bool) -> Result<(), String> {
202
+ fn run_program(
203
+ program: &tishlang_ast::Program,
204
+ backend: &str,
205
+ no_optimize: bool,
206
+ features: &[String],
207
+ ) -> Result<(), String> {
218
208
  if backend == "interp" {
219
209
  let mut eval = tishlang_eval::Evaluator::new();
220
210
  let value = eval.eval_program(program)?;
@@ -229,14 +219,22 @@ fn run_program(program: &tishlang_ast::Program, backend: &str, no_optimize: bool
229
219
  } else {
230
220
  tishlang_bytecode::compile(program).map_err(|e| e.to_string())?
231
221
  };
232
- let value = tishlang_vm::run(&chunk)?;
222
+ let caps = vm_capabilities_for_cli_run(features);
223
+ let value = tishlang_vm::run_with_options(
224
+ &chunk,
225
+ tishlang_vm::VmRunOptions {
226
+ repl_mode: false,
227
+ capabilities: caps,
228
+ },
229
+ )?;
233
230
  if !matches!(value, tishlang_core::Value::Null) {
234
231
  println!("{}", tishlang_core::format_value_styled(&value, tishlang_core::use_console_colors()));
235
232
  }
236
233
  Ok(())
237
234
  }
238
235
 
239
- fn run_repl(backend: &str, no_optimize: bool) -> Result<(), String> {
236
+ fn run_repl(backend: &str, no_optimize: bool, features: &[String]) -> Result<(), String> {
237
+ cli_help::print_tish_banner();
240
238
  println!("Tish REPL (Ctrl-D to exit)");
241
239
  let mut buffer = String::new();
242
240
 
@@ -293,7 +291,9 @@ fn run_repl(backend: &str, no_optimize: bool) -> Result<(), String> {
293
291
  if !std::io::stdin().is_terminal() {
294
292
  eprintln!("Note: Tab completion and grey preview require an interactive terminal (TTY).");
295
293
  }
296
- let vm = Rc::new(RefCell::new(tishlang_vm::Vm::new()));
294
+ let vm = Rc::new(RefCell::new(tishlang_vm::Vm::with_capabilities(
295
+ vm_capabilities_for_cli_run(features),
296
+ )));
297
297
  let completer = repl_completion::ReplCompleter {
298
298
  vm: Rc::clone(&vm),
299
299
  no_optimize,
@@ -333,7 +333,7 @@ fn run_repl(backend: &str, no_optimize: bool) -> Result<(), String> {
333
333
  tishlang_bytecode::compile_for_repl
334
334
  };
335
335
  if let Ok(chunk) = compile_fn(&program) {
336
- let _ = vm.borrow_mut().run(&chunk);
336
+ let _ = vm.borrow_mut().run_with_options(&chunk, true);
337
337
  }
338
338
  }
339
339
  Err(e) => eprintln!("Parse error: {}", e),
@@ -365,7 +365,7 @@ fn run_repl(backend: &str, no_optimize: bool) -> Result<(), String> {
365
365
  };
366
366
  match compile_fn(&program) {
367
367
  Ok(chunk) => {
368
- match vm.borrow_mut().run(&chunk) {
368
+ match vm.borrow_mut().run_with_options(&chunk, true) {
369
369
  Ok(v) => {
370
370
  if !matches!(v, tishlang_core::Value::Null) {
371
371
  println!("{}", tishlang_core::format_value_styled(&v, tishlang_core::use_console_colors()));
@@ -531,23 +531,7 @@ fn build_file(
531
531
  p
532
532
  }
533
533
  });
534
- let features: Vec<String> = if cli_features.is_empty() {
535
- #[allow(unused_mut)]
536
- let mut f = Vec::new();
537
- #[cfg(feature = "http")]
538
- f.push("http".to_string());
539
- #[cfg(feature = "fs")]
540
- f.push("fs".to_string());
541
- #[cfg(feature = "process")]
542
- f.push("process".to_string());
543
- #[cfg(feature = "regex")]
544
- f.push("regex".to_string());
545
- #[cfg(feature = "ws")]
546
- f.push("ws".to_string());
547
- f
548
- } else {
549
- cli_features.to_vec()
550
- };
534
+ let features: Vec<String> = native_build_features_from_cli(cli_features);
551
535
 
552
536
  if is_js {
553
537
  let source = fs::read_to_string(&input_path).map_err(|e| format!("{}", e))?;
@@ -592,7 +576,9 @@ fn build_file(
592
576
  mod cli_tests {
593
577
  use clap::Parser;
594
578
 
595
- use super::{argv_with_implicit_run, Cli, Commands};
579
+ use crate::cli_help::{Cli, Commands};
580
+
581
+ use super::argv_with_implicit_run;
596
582
 
597
583
  #[test]
598
584
  fn implicit_run_inserts_run_before_file() {
@@ -86,7 +86,7 @@ impl ReplCompleter {
86
86
  compile_for_repl
87
87
  };
88
88
  let chunk = compile_fn(&program).ok()?;
89
- let value = self.vm.borrow_mut().run(&chunk).ok()?;
89
+ let value = self.vm.borrow_mut().run_with_options(&chunk, true).ok()?;
90
90
 
91
91
  let keys = value.completion_keys();
92
92
  let filtered: Vec<String> = keys
@@ -521,6 +521,54 @@ fn test_mvp_programs_interpreter() {
521
521
  }
522
522
  }
523
523
 
524
+ /// Default bytecode VM must match the tree-walking interpreter for every MVP program.
525
+ #[test]
526
+ fn test_mvp_programs_interp_vm_stdout_parity() {
527
+ let core_dir = core_dir();
528
+ let bin = tish_bin();
529
+ assert!(
530
+ bin.exists(),
531
+ "tish binary not found at {}. Run `cargo build -p tishlang` first.",
532
+ bin.display()
533
+ );
534
+ for name in MVP_TEST_FILES {
535
+ let path = core_dir.join(name);
536
+ if !path.exists() {
537
+ continue;
538
+ }
539
+ let path_str = path.to_string_lossy();
540
+ let out_interp = Command::new(&bin)
541
+ .args(["run", path_str.as_ref(), "--backend", "interp"])
542
+ .current_dir(workspace_root())
543
+ .output()
544
+ .expect("run tish interpreter");
545
+ assert!(
546
+ out_interp.status.success(),
547
+ "Interpreter failed for {}: {}",
548
+ path.display(),
549
+ String::from_utf8_lossy(&out_interp.stderr)
550
+ );
551
+ let out_vm = Command::new(&bin)
552
+ .args(["run", path_str.as_ref()])
553
+ .current_dir(workspace_root())
554
+ .output()
555
+ .expect("run tish VM");
556
+ assert!(
557
+ out_vm.status.success(),
558
+ "VM failed for {}: {}",
559
+ path.display(),
560
+ String::from_utf8_lossy(&out_vm.stderr)
561
+ );
562
+ let s_interp = String::from_utf8_lossy(&out_interp.stdout);
563
+ let s_vm = String::from_utf8_lossy(&out_vm.stdout);
564
+ assert_eq!(
565
+ s_interp, s_vm,
566
+ "interp vs VM stdout mismatch for {}",
567
+ path.display()
568
+ );
569
+ }
570
+ }
571
+
524
572
  /// Compile each .tish file to native, run, and compare stdout to static expected (parallelized).
525
573
  #[test]
526
574
  fn test_mvp_programs_native() {
@@ -15,6 +15,123 @@ fn is_tish_workspace_root(root: &Path) -> bool {
15
15
  root.join("crates").join("tish_runtime").is_dir()
16
16
  }
17
17
 
18
+ /// True if `line` (trimmed) opens a Cargo.toml table whose body may contain path dependencies.
19
+ fn cargo_section_may_contain_path_deps(header: &str) -> bool {
20
+ let h = header.trim();
21
+ if h == "dependencies"
22
+ || h == "dev-dependencies"
23
+ || h == "build-dependencies"
24
+ || h == "workspace.dependencies"
25
+ {
26
+ return true;
27
+ }
28
+ h.starts_with("dependencies.")
29
+ || h.starts_with("dev-dependencies.")
30
+ || h.starts_with("build-dependencies.")
31
+ || h.starts_with("workspace.dependencies.")
32
+ || h.starts_with("patch.")
33
+ }
34
+
35
+ /// Collect `path = "..."` / `path = '...'` strings from lines in dependency-related sections.
36
+ fn path_values_from_cargo_toml(content: &str) -> Vec<String> {
37
+ let mut out = Vec::new();
38
+ let mut in_section = false;
39
+ for line in content.lines() {
40
+ let trimmed = line.trim();
41
+ if let Some(rest) = trimmed.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
42
+ in_section = cargo_section_may_contain_path_deps(rest);
43
+ continue;
44
+ }
45
+ if !in_section {
46
+ continue;
47
+ }
48
+ extract_path_assignments_from_line(trimmed, &mut out);
49
+ }
50
+ out
51
+ }
52
+
53
+ fn extract_path_assignments_from_line(line: &str, out: &mut Vec<String>) {
54
+ let mut rest = line;
55
+ while let Some(idx) = rest.find("path") {
56
+ let after = rest[idx + 4..].trim_start();
57
+ let after = match after.strip_prefix('=') {
58
+ Some(a) => a.trim_start(),
59
+ None => {
60
+ rest = &rest[idx + 4..];
61
+ continue;
62
+ }
63
+ };
64
+ let quote = match after.chars().next() {
65
+ Some('"') => '"',
66
+ Some('\'') => '\'',
67
+ _ => {
68
+ rest = &rest[idx + 4..];
69
+ continue;
70
+ }
71
+ };
72
+ let after = &after[quote.len_utf8()..];
73
+ let end = after.find(quote);
74
+ let Some(end) = end else {
75
+ rest = &rest[idx + 4..];
76
+ continue;
77
+ };
78
+ out.push(after[..end].to_string());
79
+ rest = &after[end + quote.len_utf8()..];
80
+ }
81
+ }
82
+
83
+ /// Starting from a filesystem path (crate dir or file), walk ancestors for `crates/tish_runtime`.
84
+ fn tish_root_from_path_hint(start: &Path) -> Option<PathBuf> {
85
+ let mut dir = if start.is_file() {
86
+ start.parent()?.to_path_buf()
87
+ } else {
88
+ start.to_path_buf()
89
+ };
90
+ dir = fs::canonicalize(&dir).unwrap_or(dir);
91
+ let mut cur = dir.as_path();
92
+ for _ in 0..32 {
93
+ if is_tish_workspace_root(cur) {
94
+ return Some(cur.to_path_buf());
95
+ }
96
+ cur = cur.parent()?;
97
+ }
98
+ None
99
+ }
100
+
101
+ /// Scan `dir/Cargo.toml` for path dependencies; if any resolves inside a Tish workspace, return that root.
102
+ fn tish_root_from_cargo_manifest_dir(dir: &Path) -> Option<PathBuf> {
103
+ let cargo_toml = dir.join("Cargo.toml");
104
+ if !cargo_toml.is_file() {
105
+ return None;
106
+ }
107
+ let content = fs::read_to_string(&cargo_toml).ok()?;
108
+ let base = dir;
109
+ for rel in path_values_from_cargo_toml(&content) {
110
+ let joined = base.join(&rel);
111
+ let resolved = match joined.canonicalize() {
112
+ Ok(p) => p,
113
+ Err(_) => continue,
114
+ };
115
+ if let Some(root) = tish_root_from_path_hint(&resolved) {
116
+ return Some(root);
117
+ }
118
+ }
119
+ None
120
+ }
121
+
122
+ /// Walk from `start` upward; at each directory try [`tish_root_from_cargo_manifest_dir`].
123
+ fn tish_root_from_project_cargo_files(mut start: PathBuf) -> Option<PathBuf> {
124
+ for _ in 0..32 {
125
+ if let Some(root) = tish_root_from_cargo_manifest_dir(&start) {
126
+ return Some(root);
127
+ }
128
+ if !start.pop() {
129
+ break;
130
+ }
131
+ }
132
+ None
133
+ }
134
+
18
135
  /// Find the Tish workspace root using multiple strategies.
19
136
  ///
20
137
  /// Returns the directory containing the workspace Cargo.toml (with [workspace]).
@@ -30,6 +147,10 @@ pub fn find_workspace_root() -> Result<PathBuf, String> {
30
147
  return Ok(root_buf);
31
148
  }
32
149
  }
150
+ // Consumer workspace: manifest is the app crate; path deps point at Tish checkout.
151
+ if let Some(root) = tish_root_from_project_cargo_files(path.clone()) {
152
+ return Ok(root);
153
+ }
33
154
  }
34
155
 
35
156
  // Strategy 2: Walk from current executable (e.g. target/debug/tish)
@@ -49,7 +170,14 @@ pub fn find_workspace_root() -> Result<PathBuf, String> {
49
170
  }
50
171
  }
51
172
 
52
- // Strategy 3: Walk from current working directory
173
+ // Strategy 3: Walk from current working directory (path deps on a consumer crate)
174
+ if let Ok(cwd) = std::env::current_dir() {
175
+ if let Some(root) = tish_root_from_project_cargo_files(cwd.clone()) {
176
+ return Ok(root);
177
+ }
178
+ }
179
+
180
+ // Strategy 4: Walk from current working directory
53
181
  if let Ok(mut current) = std::env::current_dir() {
54
182
  for _ in 0..15 {
55
183
  let cargo_toml = current.join("Cargo.toml");
@@ -193,3 +321,45 @@ pub fn copy_binary_to_output(binary: &Path, output_path: &Path) -> Result<(), St
193
321
  fs::copy(binary, output_path).map_err(|e| format!("Cannot copy to {}: {}", output_path.display(), e))?;
194
322
  Ok(())
195
323
  }
324
+
325
+ #[cfg(test)]
326
+ mod tests {
327
+ use super::*;
328
+
329
+ #[test]
330
+ fn path_values_dependencies_section_only() {
331
+ let toml = r#"
332
+ [package]
333
+ name = "app"
334
+ path = "ignored-outside-deps"
335
+
336
+ [dependencies]
337
+ tishlang_runtime = { path = "../tish/crates/tish_runtime" }
338
+
339
+ [metadata]
340
+ path = "also-ignored"
341
+ "#;
342
+ let paths = path_values_from_cargo_toml(toml);
343
+ assert_eq!(paths, vec!["../tish/crates/tish_runtime"]);
344
+ }
345
+
346
+ #[test]
347
+ fn path_values_workspace_dependencies() {
348
+ let toml = r#"
349
+ [workspace.dependencies]
350
+ tishlang_runtime = { path = "../../tish/tish/crates/tish_runtime" }
351
+ "#;
352
+ let paths = path_values_from_cargo_toml(toml);
353
+ assert_eq!(paths, vec!["../../tish/tish/crates/tish_runtime"]);
354
+ }
355
+
356
+ #[test]
357
+ fn path_values_patch_section() {
358
+ let toml = r#"
359
+ [patch.crates-io]
360
+ tishlang_runtime = { path = "../vendor/tish_runtime" }
361
+ "#;
362
+ let paths = path_values_from_cargo_toml(toml);
363
+ assert_eq!(paths, vec!["../vendor/tish_runtime"]);
364
+ }
365
+ }