@tishlang/tish 2.2.5 → 2.2.7
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 +0 -0
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish_ast/src/ast.rs +6 -3
- package/crates/tish_build_utils/src/lib.rs +37 -0
- package/crates/tish_compile/src/check.rs +10 -9
- package/crates/tish_compile/src/infer.rs +12 -12
- package/crates/tish_compile/src/types.rs +9 -9
- package/crates/tish_fmt/src/lib.rs +1 -1
- package/crates/tish_lsp/Cargo.toml +1 -1
- package/crates/tish_lsp/src/main.rs +58 -6
- package/crates/tish_parser/src/parser.rs +20 -9
- package/crates/tish_resolve/src/lib.rs +380 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
package/bin/tish
CHANGED
|
Binary file
|
package/crates/tish/Cargo.toml
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
use std::sync::Arc;
|
|
4
4
|
|
|
5
|
-
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
5
|
+
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
|
6
6
|
pub struct Span {
|
|
7
7
|
pub start: (usize, usize), // line, col
|
|
8
8
|
pub end: (usize, usize),
|
|
@@ -11,8 +11,11 @@ pub struct Span {
|
|
|
11
11
|
/// Type annotation for variables, parameters, and return types.
|
|
12
12
|
#[derive(Debug, Clone, PartialEq)]
|
|
13
13
|
pub enum TypeAnnotation {
|
|
14
|
-
/// Primitive types: number
|
|
15
|
-
|
|
14
|
+
/// Primitive types and type-name references: `number`, `string`, `MyAlias`. The span locates
|
|
15
|
+
/// the name token in source so type-alias rename / find-references can edit every `: T` use
|
|
16
|
+
/// (synthesized types — monomorphized generics, builtins, postfix `?` null — use a zero span
|
|
17
|
+
/// and never match a user alias). Per AST convention, spans participate in PartialEq.
|
|
18
|
+
Simple(Arc<str>, Span),
|
|
16
19
|
/// Array type: T[]
|
|
17
20
|
Array(Box<TypeAnnotation>),
|
|
18
21
|
/// Object type: { key: Type, ... }
|
|
@@ -490,9 +490,46 @@ pub fn copy_binary_to_output(binary: &Path, output_path: &Path) -> Result<(), St
|
|
|
490
490
|
}
|
|
491
491
|
fs::copy(binary, output_path)
|
|
492
492
|
.map_err(|e| format!("Cannot copy to {}: {}", output_path.display(), e))?;
|
|
493
|
+
resign_macos_adhoc(output_path);
|
|
493
494
|
Ok(())
|
|
494
495
|
}
|
|
495
496
|
|
|
497
|
+
/// Replace the linker-produced ad-hoc signature with a plain ad-hoc one on macOS.
|
|
498
|
+
///
|
|
499
|
+
/// On Apple Silicon, AMFI SIGKILLs a freshly linked binary whose ad-hoc signature carries the
|
|
500
|
+
/// `linker-signed` flag (instant exit 137, no output — looks like a crash/hang), even though
|
|
501
|
+
/// `codesign -v` reports it valid (#219). A standard `codesign --sign - --force` replaces it with a
|
|
502
|
+
/// kernel-acceptable signature. Best-effort: static archives are skipped, and a failure (codesign
|
|
503
|
+
/// absent, or a non-Mach-O output) is non-fatal — the binary is then no worse off than before.
|
|
504
|
+
#[cfg(target_os = "macos")]
|
|
505
|
+
fn resign_macos_adhoc(output_path: &Path) {
|
|
506
|
+
// Static archives (`.a`) are not code-signed.
|
|
507
|
+
if output_path.extension().is_some_and(|e| e == "a") {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
match Command::new("codesign")
|
|
511
|
+
.args(["--sign", "-", "--force"])
|
|
512
|
+
.arg(output_path)
|
|
513
|
+
.output()
|
|
514
|
+
{
|
|
515
|
+
Ok(out) if out.status.success() => {}
|
|
516
|
+
Ok(out) => eprintln!(
|
|
517
|
+
"warning: codesign --sign - failed for {} ({}); the binary may be SIGKILLed at launch on Apple Silicon — re-sign with `codesign -s - -f {}`",
|
|
518
|
+
output_path.display(),
|
|
519
|
+
String::from_utf8_lossy(&out.stderr).trim(),
|
|
520
|
+
output_path.display()
|
|
521
|
+
),
|
|
522
|
+
Err(e) => eprintln!(
|
|
523
|
+
"warning: could not run codesign for {} ({e}); if the binary is killed at launch, re-sign with `codesign -s - -f {}`",
|
|
524
|
+
output_path.display(),
|
|
525
|
+
output_path.display()
|
|
526
|
+
),
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
#[cfg(not(target_os = "macos"))]
|
|
531
|
+
fn resign_macos_adhoc(_output_path: &Path) {}
|
|
532
|
+
|
|
496
533
|
#[cfg(test)]
|
|
497
534
|
mod tests {
|
|
498
535
|
use super::*;
|
|
@@ -29,6 +29,7 @@ fn lit_base(lit: &TypeLiteral) -> TypeAnnotation {
|
|
|
29
29
|
TypeLiteral::Bool(_) => "boolean",
|
|
30
30
|
}
|
|
31
31
|
.into(),
|
|
32
|
+
tishlang_ast::Span::default(),
|
|
32
33
|
)
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -70,19 +71,19 @@ pub fn check_program(program: &Program) -> Vec<TypeDiagnostic> {
|
|
|
70
71
|
// ── helpers: type constructors / predicates ─────────────────────────────────────────────────
|
|
71
72
|
|
|
72
73
|
fn simple(s: &str) -> TypeAnnotation {
|
|
73
|
-
TypeAnnotation::Simple(s.into())
|
|
74
|
+
TypeAnnotation::Simple(s.into(), tishlang_ast::Span::default())
|
|
74
75
|
}
|
|
75
76
|
fn is_any(ann: &TypeAnnotation) -> bool {
|
|
76
|
-
matches!(ann, TypeAnnotation::Simple(s) if s.as_ref() == "any")
|
|
77
|
+
matches!(ann, TypeAnnotation::Simple(s, _) if s.as_ref() == "any")
|
|
77
78
|
}
|
|
78
79
|
fn is_named(ann: &TypeAnnotation, n: &str) -> bool {
|
|
79
|
-
matches!(ann, TypeAnnotation::Simple(s) if s.as_ref() == n)
|
|
80
|
+
matches!(ann, TypeAnnotation::Simple(s, _) if s.as_ref() == n)
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
/// Display a type for diagnostics (close to the source syntax).
|
|
83
84
|
fn show(ann: &TypeAnnotation) -> String {
|
|
84
85
|
match ann {
|
|
85
|
-
TypeAnnotation::Simple(s) => s.to_string(),
|
|
86
|
+
TypeAnnotation::Simple(s, _) => s.to_string(),
|
|
86
87
|
TypeAnnotation::Array(t) => format!("{}[]", show(t)),
|
|
87
88
|
TypeAnnotation::Object(fs) => {
|
|
88
89
|
let inner: Vec<String> = fs.iter().map(|(k, t)| format!("{}: {}", k, show(t))).collect();
|
|
@@ -116,7 +117,7 @@ fn resolve<'a>(
|
|
|
116
117
|
if depth > 8 {
|
|
117
118
|
return ann;
|
|
118
119
|
}
|
|
119
|
-
if let TypeAnnotation::Simple(s) = ann {
|
|
120
|
+
if let TypeAnnotation::Simple(s, _) = ann {
|
|
120
121
|
if let Some(t) = aliases.get(s.as_ref()) {
|
|
121
122
|
return resolve(t, aliases, depth + 1);
|
|
122
123
|
}
|
|
@@ -138,12 +139,12 @@ fn assignable(
|
|
|
138
139
|
}
|
|
139
140
|
// `null`/`void`/`undefined` are leniently compatible (tish uses `null` for optionals; checking
|
|
140
141
|
// it strictly would false-positive without real union/optional support).
|
|
141
|
-
if matches!(a, TypeAnnotation::Simple(s) if matches!(s.as_ref(), "null" | "void" | "undefined")) {
|
|
142
|
+
if matches!(a, TypeAnnotation::Simple(s, _) if matches!(s.as_ref(), "null" | "void" | "undefined")) {
|
|
142
143
|
return true;
|
|
143
144
|
}
|
|
144
145
|
use TypeAnnotation::*;
|
|
145
146
|
match (a, e) {
|
|
146
|
-
(Simple(x), Simple(y)) => {
|
|
147
|
+
(Simple(x, _), Simple(y, _)) => {
|
|
147
148
|
// Strict only among the three scalar primitives; any user-defined / unresolved name is
|
|
148
149
|
// treated as compatible.
|
|
149
150
|
let strict = |s: &str| matches!(s, "number" | "string" | "boolean");
|
|
@@ -155,7 +156,7 @@ fn assignable(
|
|
|
155
156
|
}
|
|
156
157
|
(Array(ax), Array(ey)) => assignable(ax, ey, aliases),
|
|
157
158
|
// array vs non-array (after alias/any resolution) is a clear mismatch
|
|
158
|
-
(Array(_), Simple(_)) | (Simple(_), Array(_)) => false,
|
|
159
|
+
(Array(_), Simple(_, _)) | (Simple(_, _), Array(_)) => false,
|
|
159
160
|
(Object(af), Object(ef)) => ef.iter().all(|(k, et)| {
|
|
160
161
|
af.iter()
|
|
161
162
|
.find(|(ak, _)| ak.as_ref() == k.as_ref())
|
|
@@ -510,7 +511,7 @@ impl CheckCtx {
|
|
|
510
511
|
TypeAnnotation::Array(_) if name.as_ref() == "length" => {
|
|
511
512
|
return Some(simple("number"));
|
|
512
513
|
}
|
|
513
|
-
TypeAnnotation::Simple(s)
|
|
514
|
+
TypeAnnotation::Simple(s, _)
|
|
514
515
|
if s.as_ref() == "string" && name.as_ref() == "length" =>
|
|
515
516
|
{
|
|
516
517
|
return Some(simple("number"));
|
|
@@ -52,15 +52,15 @@ impl InferCtx {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
fn is_number(ann: &TypeAnnotation) -> bool {
|
|
55
|
-
matches!(ann, TypeAnnotation::Simple(s) if s.as_ref() == "number")
|
|
55
|
+
matches!(ann, TypeAnnotation::Simple(s, _) if s.as_ref() == "number")
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
fn is_string(ann: &TypeAnnotation) -> bool {
|
|
59
|
-
matches!(ann, TypeAnnotation::Simple(s) if s.as_ref() == "string")
|
|
59
|
+
matches!(ann, TypeAnnotation::Simple(s, _) if s.as_ref() == "string")
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
fn is_bool(ann: &TypeAnnotation) -> bool {
|
|
63
|
-
matches!(ann, TypeAnnotation::Simple(s) if s.as_ref() == "boolean")
|
|
63
|
+
matches!(ann, TypeAnnotation::Simple(s, _) if s.as_ref() == "boolean")
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/// Element type of an array literal of uniform native scalars (number/string/boolean), for
|
|
@@ -91,15 +91,15 @@ fn infer_array_elem(elements: &[tishlang_ast::ArrayElement], ctx: &InferCtx) ->
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
fn number_ann() -> TypeAnnotation {
|
|
94
|
-
TypeAnnotation::Simple("number".into())
|
|
94
|
+
TypeAnnotation::Simple("number".into(), tishlang_ast::Span::default())
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
fn string_ann() -> TypeAnnotation {
|
|
98
|
-
TypeAnnotation::Simple("string".into())
|
|
98
|
+
TypeAnnotation::Simple("string".into(), tishlang_ast::Span::default())
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
fn bool_ann() -> TypeAnnotation {
|
|
102
|
-
TypeAnnotation::Simple("boolean".into())
|
|
102
|
+
TypeAnnotation::Simple("boolean".into(), tishlang_ast::Span::default())
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
/// Infer the `TypeAnnotation` for an expression, if unambiguous.
|
|
@@ -237,7 +237,7 @@ fn pi_stmt(s: Statement) -> Statement {
|
|
|
237
237
|
&& tp.default.is_none()
|
|
238
238
|
&& nus_stmt(&body, tp.name.as_ref(), &nums)
|
|
239
239
|
{
|
|
240
|
-
tp.type_ann = Some(TypeAnnotation::Simple(std::sync::Arc::from("number")));
|
|
240
|
+
tp.type_ann = Some(TypeAnnotation::Simple(std::sync::Arc::from("number"), tishlang_ast::Span::default()));
|
|
241
241
|
}
|
|
242
242
|
FunParam::Simple(tp)
|
|
243
243
|
}
|
|
@@ -612,7 +612,7 @@ impl StructRegistry {
|
|
|
612
612
|
|
|
613
613
|
fn type_canon(t: &TypeAnnotation) -> String {
|
|
614
614
|
match t {
|
|
615
|
-
TypeAnnotation::Simple(s) => s.to_string(),
|
|
615
|
+
TypeAnnotation::Simple(s, _) => s.to_string(),
|
|
616
616
|
TypeAnnotation::Object(fields) => format!(
|
|
617
617
|
"{{{}}}",
|
|
618
618
|
fields
|
|
@@ -637,7 +637,7 @@ fn infer_object_shape(
|
|
|
637
637
|
tishlang_ast::ObjectProp::KeyValue(k, v) => {
|
|
638
638
|
let ty = infer_expr_type(v, ctx)?;
|
|
639
639
|
// Only primitive field types in this conservative version.
|
|
640
|
-
if !matches!(&ty, TypeAnnotation::Simple(s)
|
|
640
|
+
if !matches!(&ty, TypeAnnotation::Simple(s, _)
|
|
641
641
|
if matches!(s.as_ref(), "number" | "string" | "boolean"))
|
|
642
642
|
{
|
|
643
643
|
return None;
|
|
@@ -776,12 +776,12 @@ fn si_block(stmts: Vec<Statement>, reg: &mut StructRegistry, ctx: &mut InferCtx)
|
|
|
776
776
|
// Sound: every later use in this block must be a literal-key read.
|
|
777
777
|
if uses_are_struct_safe(name.as_ref(), &keys, &stmts[i + 1..]) {
|
|
778
778
|
let alias = reg.intern(&fields);
|
|
779
|
-
ctx.define(name.as_ref(), TypeAnnotation::Simple(alias.as_str().into()));
|
|
779
|
+
ctx.define(name.as_ref(), TypeAnnotation::Simple(alias.as_str().into(), tishlang_ast::Span::default()));
|
|
780
780
|
out.push(Statement::VarDecl {
|
|
781
781
|
name: name.clone(),
|
|
782
782
|
name_span: *name_span,
|
|
783
783
|
mutable: *mutable,
|
|
784
|
-
type_ann: Some(TypeAnnotation::Simple(alias.as_str().into())),
|
|
784
|
+
type_ann: Some(TypeAnnotation::Simple(alias.as_str().into(), tishlang_ast::Span::default())),
|
|
785
785
|
init: stmt_init_clone(stmt),
|
|
786
786
|
span: *span,
|
|
787
787
|
});
|
|
@@ -1642,7 +1642,7 @@ mod param_infer_tests {
|
|
|
1642
1642
|
if let FunParam::Simple(tp) = p {
|
|
1643
1643
|
if tp.name.as_ref() == param {
|
|
1644
1644
|
return tp.type_ann.as_ref().map(|a| match a {
|
|
1645
|
-
TypeAnnotation::Simple(s) => s.to_string(),
|
|
1645
|
+
TypeAnnotation::Simple(s, _) => s.to_string(),
|
|
1646
1646
|
_ => "<complex>".to_string(),
|
|
1647
1647
|
});
|
|
1648
1648
|
}
|
|
@@ -62,7 +62,7 @@ impl RustType {
|
|
|
62
62
|
aliases: &HashMap<String, RustType>,
|
|
63
63
|
) -> Self {
|
|
64
64
|
match ann {
|
|
65
|
-
TypeAnnotation::Simple(name) => match name.as_ref() {
|
|
65
|
+
TypeAnnotation::Simple(name, _) => match name.as_ref() {
|
|
66
66
|
"number" => RustType::F64,
|
|
67
67
|
"string" => RustType::String,
|
|
68
68
|
"boolean" | "bool" => RustType::Bool,
|
|
@@ -111,10 +111,10 @@ impl RustType {
|
|
|
111
111
|
if types.len() == 2 {
|
|
112
112
|
let has_null = types
|
|
113
113
|
.iter()
|
|
114
|
-
.any(|t| matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null"));
|
|
114
|
+
.any(|t| matches!(t, TypeAnnotation::Simple(s, _) if s.as_ref() == "null"));
|
|
115
115
|
if has_null {
|
|
116
116
|
let non_null = types.iter().find(
|
|
117
|
-
|t| !matches!(t, TypeAnnotation::Simple(s) if s.as_ref() == "null"),
|
|
117
|
+
|t| !matches!(t, TypeAnnotation::Simple(s, _) if s.as_ref() == "null"),
|
|
118
118
|
);
|
|
119
119
|
if let Some(inner) = non_null {
|
|
120
120
|
return RustType::Option(Box::new(Self::from_annotation_with_aliases(
|
|
@@ -532,22 +532,22 @@ mod tests {
|
|
|
532
532
|
#[test]
|
|
533
533
|
fn test_simple_types() {
|
|
534
534
|
assert_eq!(
|
|
535
|
-
RustType::from_annotation(&TypeAnnotation::Simple("number".into())),
|
|
535
|
+
RustType::from_annotation(&TypeAnnotation::Simple("number".into(), tishlang_ast::Span::default())),
|
|
536
536
|
RustType::F64
|
|
537
537
|
);
|
|
538
538
|
assert_eq!(
|
|
539
|
-
RustType::from_annotation(&TypeAnnotation::Simple("string".into())),
|
|
539
|
+
RustType::from_annotation(&TypeAnnotation::Simple("string".into(), tishlang_ast::Span::default())),
|
|
540
540
|
RustType::String
|
|
541
541
|
);
|
|
542
542
|
assert_eq!(
|
|
543
|
-
RustType::from_annotation(&TypeAnnotation::Simple("boolean".into())),
|
|
543
|
+
RustType::from_annotation(&TypeAnnotation::Simple("boolean".into(), tishlang_ast::Span::default())),
|
|
544
544
|
RustType::Bool
|
|
545
545
|
);
|
|
546
546
|
}
|
|
547
547
|
|
|
548
548
|
#[test]
|
|
549
549
|
fn test_array_type() {
|
|
550
|
-
let arr_type = TypeAnnotation::Array(Box::new(TypeAnnotation::Simple("number".into())));
|
|
550
|
+
let arr_type = TypeAnnotation::Array(Box::new(TypeAnnotation::Simple("number".into(), tishlang_ast::Span::default())));
|
|
551
551
|
assert_eq!(
|
|
552
552
|
RustType::from_annotation(&arr_type),
|
|
553
553
|
RustType::Vec(Box::new(RustType::F64))
|
|
@@ -557,8 +557,8 @@ mod tests {
|
|
|
557
557
|
#[test]
|
|
558
558
|
fn test_nullable_type() {
|
|
559
559
|
let nullable = TypeAnnotation::Union(vec![
|
|
560
|
-
TypeAnnotation::Simple("string".into()),
|
|
561
|
-
TypeAnnotation::Simple("null".into()),
|
|
560
|
+
TypeAnnotation::Simple("string".into(), tishlang_ast::Span::default()),
|
|
561
|
+
TypeAnnotation::Simple("null".into(), tishlang_ast::Span::default()),
|
|
562
562
|
]);
|
|
563
563
|
assert_eq!(
|
|
564
564
|
RustType::from_annotation(&nullable),
|
|
@@ -1046,7 +1046,7 @@ impl Printer {
|
|
|
1046
1046
|
|
|
1047
1047
|
fn type_ann(&mut self, t: &TypeAnnotation) {
|
|
1048
1048
|
match t {
|
|
1049
|
-
TypeAnnotation::Simple(s) => self.buf.push_str(s.as_ref()),
|
|
1049
|
+
TypeAnnotation::Simple(s, _) => self.buf.push_str(s.as_ref()),
|
|
1050
1050
|
TypeAnnotation::Array(inner) => {
|
|
1051
1051
|
self.type_ann(inner);
|
|
1052
1052
|
self.buf.push_str("[]");
|
|
@@ -19,7 +19,7 @@ tishlang_fmt = { path = "../tish_fmt", version = ">=0.1" }
|
|
|
19
19
|
tishlang_lint = { path = "../tish_lint", version = ">=0.1" }
|
|
20
20
|
tishlang_resolve = { path = "../tish_resolve", version = ">=0.1" }
|
|
21
21
|
tower-lsp = "0.20"
|
|
22
|
-
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-std"] }
|
|
22
|
+
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-std", "time"] }
|
|
23
23
|
serde_json = "1"
|
|
24
24
|
regex = "1"
|
|
25
25
|
walkdir = "2"
|
|
@@ -29,6 +29,10 @@ mod import_goto;
|
|
|
29
29
|
struct Backend {
|
|
30
30
|
client: Client,
|
|
31
31
|
docs: Arc<RwLock<HashMap<Url, String>>>,
|
|
32
|
+
/// Monotonic per-document edit counter. did_change bumps it and a debounced task only
|
|
33
|
+
/// publishes diagnostics if its edit is still the latest — so rapid keystrokes coalesce and
|
|
34
|
+
/// superseded recomputes are dropped (the analysis pipeline is comparatively expensive).
|
|
35
|
+
edit_seq: Arc<RwLock<HashMap<Url, u64>>>,
|
|
32
36
|
roots: Arc<RwLock<Vec<PathBuf>>>,
|
|
33
37
|
/// `(project_root, cargo:spec)` → resolved dependency source root (for `cargo metadata` / registry).
|
|
34
38
|
cargo_src_cache: Arc<RwLock<HashMap<(PathBuf, String), PathBuf>>>,
|
|
@@ -44,6 +48,7 @@ async fn main() {
|
|
|
44
48
|
let (service, socket) = LspService::new(|client| Backend {
|
|
45
49
|
client,
|
|
46
50
|
docs: Arc::new(RwLock::new(HashMap::new())),
|
|
51
|
+
edit_seq: Arc::new(RwLock::new(HashMap::new())),
|
|
47
52
|
roots: Arc::new(RwLock::new(Vec::new())),
|
|
48
53
|
cargo_src_cache: Arc::new(RwLock::new(HashMap::new())),
|
|
49
54
|
tishlang_source_root: Arc::new(RwLock::new(None)),
|
|
@@ -310,12 +315,35 @@ impl LanguageServer for Backend {
|
|
|
310
315
|
.write()
|
|
311
316
|
.unwrap()
|
|
312
317
|
.insert(uri.clone(), chg.text.clone());
|
|
313
|
-
|
|
318
|
+
// Bump this document's edit sequence and debounce the (expensive) analysis: only the
|
|
319
|
+
// task whose sequence is still current after the delay publishes, so a burst of
|
|
320
|
+
// keystrokes coalesces into one recompute instead of one-per-change.
|
|
321
|
+
let seq = {
|
|
322
|
+
let mut g = self.edit_seq.write().unwrap();
|
|
323
|
+
let n = g.entry(uri.clone()).or_insert(0);
|
|
324
|
+
*n += 1;
|
|
325
|
+
*n
|
|
326
|
+
};
|
|
327
|
+
let client = self.client.clone();
|
|
328
|
+
let docs = Arc::clone(&self.docs);
|
|
329
|
+
let edit_seq = Arc::clone(&self.edit_seq);
|
|
330
|
+
tokio::spawn(async move {
|
|
331
|
+
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
|
|
332
|
+
// Superseded by a newer edit while we waited — drop this stale recompute.
|
|
333
|
+
if edit_seq.read().unwrap().get(&uri).copied() != Some(seq) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
let text = docs.read().unwrap().get(&uri).cloned();
|
|
337
|
+
if let Some(text) = text {
|
|
338
|
+
publish_parse_and_lint(&client, uri, &text).await;
|
|
339
|
+
}
|
|
340
|
+
});
|
|
314
341
|
}
|
|
315
342
|
}
|
|
316
343
|
|
|
317
344
|
async fn did_close(&self, p: DidCloseTextDocumentParams) {
|
|
318
345
|
self.docs.write().unwrap().remove(&p.text_document.uri);
|
|
346
|
+
self.edit_seq.write().unwrap().remove(&p.text_document.uri);
|
|
319
347
|
self.client
|
|
320
348
|
.publish_diagnostics(p.text_document.uri, vec![], None)
|
|
321
349
|
.await;
|
|
@@ -700,6 +728,30 @@ impl LanguageServer for Backend {
|
|
|
700
728
|
let Ok(program) = tishlang_parser::parse(&text) else {
|
|
701
729
|
return Ok(None);
|
|
702
730
|
};
|
|
731
|
+
// Type-alias rename: the value resolver can't see `: T` annotation uses (type names live
|
|
732
|
+
// in a separate namespace), so handle a cursor on a type-alias declaration/use here,
|
|
733
|
+
// editing the declaration and every annotation site together.
|
|
734
|
+
if let Some(spans) =
|
|
735
|
+
tishlang_resolve::type_alias_rename_spans(&program, &text, pos.line, pos.character)
|
|
736
|
+
{
|
|
737
|
+
let mut edits: Vec<TextEdit> = spans
|
|
738
|
+
.into_iter()
|
|
739
|
+
.map(|sp| TextEdit {
|
|
740
|
+
range: span_to_range(&sp, &text),
|
|
741
|
+
new_text: new_name.clone(),
|
|
742
|
+
})
|
|
743
|
+
.collect();
|
|
744
|
+
edits.sort_by(|a, b| {
|
|
745
|
+
(b.range.start.line, b.range.start.character)
|
|
746
|
+
.cmp(&(a.range.start.line, a.range.start.character))
|
|
747
|
+
});
|
|
748
|
+
let mut m = HashMap::new();
|
|
749
|
+
m.insert(uri.clone(), edits);
|
|
750
|
+
return Ok(Some(WorkspaceEdit {
|
|
751
|
+
changes: Some(m),
|
|
752
|
+
..Default::default()
|
|
753
|
+
}));
|
|
754
|
+
}
|
|
703
755
|
let Some(def) = tishlang_resolve::definition_span(&program, &text, pos.line, pos.character)
|
|
704
756
|
else {
|
|
705
757
|
return Ok(None);
|
|
@@ -1074,7 +1126,7 @@ fn is_ident_char(c: char) -> bool {
|
|
|
1074
1126
|
fn render_type(t: &tishlang_ast::TypeAnnotation) -> String {
|
|
1075
1127
|
use tishlang_ast::{TypeAnnotation as T, TypeLiteral as L};
|
|
1076
1128
|
match t {
|
|
1077
|
-
T::Simple(s) => s.to_string(),
|
|
1129
|
+
T::Simple(s, _) => s.to_string(),
|
|
1078
1130
|
T::Array(inner) => {
|
|
1079
1131
|
// Parenthesize composite element types so `(A | B)[]` reads unambiguously.
|
|
1080
1132
|
if matches!(
|
|
@@ -1128,7 +1180,7 @@ fn shallow_expr_type(e: &tishlang_ast::Expr) -> Option<tishlang_ast::TypeAnnotat
|
|
|
1128
1180
|
Literal::Bool(_) => "boolean",
|
|
1129
1181
|
Literal::Null => "null",
|
|
1130
1182
|
};
|
|
1131
|
-
Some(T::Simple(Arc::from(name)))
|
|
1183
|
+
Some(T::Simple(Arc::from(name), tishlang_ast::Span::default()))
|
|
1132
1184
|
} else {
|
|
1133
1185
|
None
|
|
1134
1186
|
}
|
|
@@ -1661,11 +1713,11 @@ mod hover_tests {
|
|
|
1661
1713
|
#[test]
|
|
1662
1714
|
fn composite_types_render() {
|
|
1663
1715
|
use tishlang_ast::{TypeAnnotation as T, TypeLiteral as L};
|
|
1664
|
-
let arr = T::Array(Box::new(T::Simple("number".into())));
|
|
1716
|
+
let arr = T::Array(Box::new(T::Simple("number".into(), tishlang_ast::Span::default())));
|
|
1665
1717
|
assert_eq!(render_type(&arr), "number[]");
|
|
1666
|
-
let tup = T::Tuple(vec![T::Simple("number".into()), T::Simple("string".into())]);
|
|
1718
|
+
let tup = T::Tuple(vec![T::Simple("number".into(), tishlang_ast::Span::default()), T::Simple("string".into(), tishlang_ast::Span::default())]);
|
|
1667
1719
|
assert_eq!(render_type(&tup), "[number, string]");
|
|
1668
|
-
let uni = T::Union(vec![T::Simple("number".into()), T::Simple("null".into())]);
|
|
1720
|
+
let uni = T::Union(vec![T::Simple("number".into(), tishlang_ast::Span::default()), T::Simple("null".into(), tishlang_ast::Span::default())]);
|
|
1669
1721
|
assert_eq!(render_type(&uni), "number | null");
|
|
1670
1722
|
assert_eq!(render_type(&T::Literal(L::Str("on".into()))), "\"on\"");
|
|
1671
1723
|
let arr_of_union = T::Array(Box::new(uni));
|
|
@@ -68,7 +68,7 @@ fn mangle_generic(base: &str, args: &[TypeAnnotation]) -> String {
|
|
|
68
68
|
|
|
69
69
|
fn mangle_type(t: &TypeAnnotation) -> String {
|
|
70
70
|
match t {
|
|
71
|
-
TypeAnnotation::Simple(s) => s.chars().map(|c| if c.is_alphanumeric() { c } else { '_' }).collect(),
|
|
71
|
+
TypeAnnotation::Simple(s, _) => s.chars().map(|c| if c.is_alphanumeric() { c } else { '_' }).collect(),
|
|
72
72
|
TypeAnnotation::Array(inner) => format!("{}Arr", mangle_type(inner)),
|
|
73
73
|
TypeAnnotation::Tuple(es) => {
|
|
74
74
|
format!("Tup{}", es.iter().map(mangle_type).collect::<Vec<_>>().join(""))
|
|
@@ -84,7 +84,7 @@ fn mangle_type(t: &TypeAnnotation) -> String {
|
|
|
84
84
|
/// Substitute generic type parameters with their concrete arguments throughout a type body.
|
|
85
85
|
fn subst_type(t: &TypeAnnotation, map: &HashMap<&str, &TypeAnnotation>) -> TypeAnnotation {
|
|
86
86
|
match t {
|
|
87
|
-
TypeAnnotation::Simple(s) => map
|
|
87
|
+
TypeAnnotation::Simple(s, _) => map
|
|
88
88
|
.get(s.as_ref())
|
|
89
89
|
.map(|rep| (*rep).clone())
|
|
90
90
|
.unwrap_or_else(|| t.clone()),
|
|
@@ -758,7 +758,10 @@ impl<'a> Parser<'a> {
|
|
|
758
758
|
}
|
|
759
759
|
Some(TokenKind::Question) => {
|
|
760
760
|
self.advance(); // ?
|
|
761
|
-
t = TypeAnnotation::Union(vec![
|
|
761
|
+
t = TypeAnnotation::Union(vec![
|
|
762
|
+
t,
|
|
763
|
+
TypeAnnotation::Simple("null".into(), Span::default()),
|
|
764
|
+
]);
|
|
762
765
|
}
|
|
763
766
|
_ => break,
|
|
764
767
|
}
|
|
@@ -772,6 +775,10 @@ impl<'a> Parser<'a> {
|
|
|
772
775
|
Some(TokenKind::Ident) => {
|
|
773
776
|
let tok = self.advance().ok_or("Expected type name")?;
|
|
774
777
|
let name = tok.literal.clone().ok_or("Expected type name")?;
|
|
778
|
+
let span = Span {
|
|
779
|
+
start: tok.span.start,
|
|
780
|
+
end: tok.span.end,
|
|
781
|
+
};
|
|
775
782
|
// Generic reference `Name<Args>`: `Array<T>` desugars to the native `T[]`; other
|
|
776
783
|
// generic refs erase their args (the base name resolves to its alias, whose type
|
|
777
784
|
// params already act as unknown -> `Value`).
|
|
@@ -786,25 +793,29 @@ impl<'a> Parser<'a> {
|
|
|
786
793
|
// alias `Box__number` (a native struct). Falls back to erasing the args when
|
|
787
794
|
// `name` isn't a known generic alias (e.g. forward reference) or arity mismatches.
|
|
788
795
|
if let Some(spec) = self.monomorphize_generic(name.as_ref(), &args) {
|
|
789
|
-
return Ok(TypeAnnotation::Simple(Arc::from(spec.as_str())));
|
|
796
|
+
return Ok(TypeAnnotation::Simple(Arc::from(spec.as_str()), span));
|
|
790
797
|
}
|
|
791
|
-
return Ok(TypeAnnotation::Simple(name));
|
|
798
|
+
return Ok(TypeAnnotation::Simple(name, span));
|
|
792
799
|
}
|
|
793
|
-
Ok(TypeAnnotation::Simple(name))
|
|
800
|
+
Ok(TypeAnnotation::Simple(name, span))
|
|
794
801
|
}
|
|
795
802
|
Some(TokenKind::Type | TokenKind::Declare) => {
|
|
796
803
|
let tok = self.advance().ok_or("Expected type name")?;
|
|
797
804
|
let name = tok.literal.clone().ok_or("Expected type name")?;
|
|
798
|
-
|
|
805
|
+
let span = Span {
|
|
806
|
+
start: tok.span.start,
|
|
807
|
+
end: tok.span.end,
|
|
808
|
+
};
|
|
809
|
+
Ok(TypeAnnotation::Simple(name, span))
|
|
799
810
|
}
|
|
800
811
|
// Handle keywords that can be type names
|
|
801
812
|
Some(TokenKind::Null) => {
|
|
802
813
|
self.advance();
|
|
803
|
-
Ok(TypeAnnotation::Simple("null".into()))
|
|
814
|
+
Ok(TypeAnnotation::Simple("null".into(), Span::default()))
|
|
804
815
|
}
|
|
805
816
|
Some(TokenKind::Void) => {
|
|
806
817
|
self.advance();
|
|
807
|
-
Ok(TypeAnnotation::Simple("void".into()))
|
|
818
|
+
Ok(TypeAnnotation::Simple("void".into(), Span::default()))
|
|
808
819
|
}
|
|
809
820
|
Some(TokenKind::LBrace) => {
|
|
810
821
|
// Object type: { key: Type, ... }
|
|
@@ -3570,6 +3570,358 @@ pub fn shallow_module_bindings(program: &Program) -> Vec<(Arc<str>, tishlang_ast
|
|
|
3570
3570
|
}
|
|
3571
3571
|
|
|
3572
3572
|
/// All spans (definition + uses) that resolve to `def_span` for `name`.
|
|
3573
|
+
// ---- Type-alias rename (#131) ---------------------------------------------------------------
|
|
3574
|
+
// Type names live in a namespace separate from value bindings, so the value resolver
|
|
3575
|
+
// (`definition_span` / `reference_spans_for_def`) never sees `: T` annotation uses. These walkers
|
|
3576
|
+
// gather every type-name occurrence — the alias declaration plus each span-carrying
|
|
3577
|
+
// `TypeAnnotation::Simple` reference in any annotation — so renaming a type alias edits the
|
|
3578
|
+
// declaration and all its uses together (previously only the declaration was edited, silently
|
|
3579
|
+
// breaking the program).
|
|
3580
|
+
|
|
3581
|
+
struct TypeOcc {
|
|
3582
|
+
name: Arc<str>,
|
|
3583
|
+
span: tishlang_ast::Span,
|
|
3584
|
+
is_decl: bool,
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3587
|
+
fn tref_ann(ann: &tishlang_ast::TypeAnnotation, out: &mut Vec<TypeOcc>) {
|
|
3588
|
+
use tishlang_ast::TypeAnnotation as T;
|
|
3589
|
+
match ann {
|
|
3590
|
+
T::Simple(name, span) => out.push(TypeOcc {
|
|
3591
|
+
name: name.clone(),
|
|
3592
|
+
span: *span,
|
|
3593
|
+
is_decl: false,
|
|
3594
|
+
}),
|
|
3595
|
+
T::Array(inner) => tref_ann(inner, out),
|
|
3596
|
+
T::Object(fields) => {
|
|
3597
|
+
for (_, t) in fields {
|
|
3598
|
+
tref_ann(t, out);
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
T::Function { params, returns } => {
|
|
3602
|
+
for p in params {
|
|
3603
|
+
tref_ann(p, out);
|
|
3604
|
+
}
|
|
3605
|
+
tref_ann(returns, out);
|
|
3606
|
+
}
|
|
3607
|
+
T::Union(ts) | T::Tuple(ts) | T::Intersection(ts) => {
|
|
3608
|
+
for t in ts {
|
|
3609
|
+
tref_ann(t, out);
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
T::Literal(_) => {}
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
fn tref_typed_param(tp: &TypedParam, out: &mut Vec<TypeOcc>) {
|
|
3617
|
+
if let Some(t) = &tp.type_ann {
|
|
3618
|
+
tref_ann(t, out);
|
|
3619
|
+
}
|
|
3620
|
+
if let Some(d) = &tp.default {
|
|
3621
|
+
tref_expr(d, out);
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
fn tref_param(p: &FunParam, out: &mut Vec<TypeOcc>) {
|
|
3626
|
+
match p {
|
|
3627
|
+
FunParam::Simple(tp) => tref_typed_param(tp, out),
|
|
3628
|
+
FunParam::Destructure {
|
|
3629
|
+
type_ann, default, ..
|
|
3630
|
+
} => {
|
|
3631
|
+
if let Some(t) = type_ann {
|
|
3632
|
+
tref_ann(t, out);
|
|
3633
|
+
}
|
|
3634
|
+
if let Some(d) = default {
|
|
3635
|
+
tref_expr(d, out);
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
fn tref_arrow_body(body: &ArrowBody, out: &mut Vec<TypeOcc>) {
|
|
3642
|
+
match body {
|
|
3643
|
+
ArrowBody::Expr(e) => tref_expr(e, out),
|
|
3644
|
+
ArrowBody::Block(s) => tref_stmt(s, out),
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3648
|
+
fn tref_expr(expr: &Expr, out: &mut Vec<TypeOcc>) {
|
|
3649
|
+
match expr {
|
|
3650
|
+
Expr::ArrowFunction { params, body, .. } => {
|
|
3651
|
+
for p in params {
|
|
3652
|
+
tref_param(p, out);
|
|
3653
|
+
}
|
|
3654
|
+
tref_arrow_body(body, out);
|
|
3655
|
+
}
|
|
3656
|
+
Expr::Binary { left, right, .. } | Expr::NullishCoalesce { left, right, .. } => {
|
|
3657
|
+
tref_expr(left, out);
|
|
3658
|
+
tref_expr(right, out);
|
|
3659
|
+
}
|
|
3660
|
+
Expr::Unary { operand, .. }
|
|
3661
|
+
| Expr::TypeOf { operand, .. }
|
|
3662
|
+
| Expr::Await { operand, .. } => tref_expr(operand, out),
|
|
3663
|
+
Expr::Delete { target, .. } => tref_expr(target, out),
|
|
3664
|
+
Expr::Call { callee, args, .. } | Expr::New { callee, args, .. } => {
|
|
3665
|
+
tref_expr(callee, out);
|
|
3666
|
+
for a in args {
|
|
3667
|
+
match a {
|
|
3668
|
+
CallArg::Expr(e) | CallArg::Spread(e) => tref_expr(e, out),
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
Expr::Member { object, .. } => tref_expr(object, out),
|
|
3673
|
+
Expr::Index { object, index, .. } => {
|
|
3674
|
+
tref_expr(object, out);
|
|
3675
|
+
tref_expr(index, out);
|
|
3676
|
+
}
|
|
3677
|
+
Expr::Conditional {
|
|
3678
|
+
cond,
|
|
3679
|
+
then_branch,
|
|
3680
|
+
else_branch,
|
|
3681
|
+
..
|
|
3682
|
+
} => {
|
|
3683
|
+
tref_expr(cond, out);
|
|
3684
|
+
tref_expr(then_branch, out);
|
|
3685
|
+
tref_expr(else_branch, out);
|
|
3686
|
+
}
|
|
3687
|
+
Expr::Array { elements, .. } => {
|
|
3688
|
+
for el in elements {
|
|
3689
|
+
match el {
|
|
3690
|
+
tishlang_ast::ArrayElement::Expr(e)
|
|
3691
|
+
| tishlang_ast::ArrayElement::Spread(e) => tref_expr(e, out),
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
Expr::Object { props, .. } => {
|
|
3696
|
+
for p in props {
|
|
3697
|
+
match p {
|
|
3698
|
+
tishlang_ast::ObjectProp::KeyValue(_, e)
|
|
3699
|
+
| tishlang_ast::ObjectProp::Spread(e) => tref_expr(e, out),
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
Expr::Assign { value, .. }
|
|
3704
|
+
| Expr::CompoundAssign { value, .. }
|
|
3705
|
+
| Expr::LogicalAssign { value, .. } => tref_expr(value, out),
|
|
3706
|
+
Expr::MemberAssign { object, value, .. } => {
|
|
3707
|
+
tref_expr(object, out);
|
|
3708
|
+
tref_expr(value, out);
|
|
3709
|
+
}
|
|
3710
|
+
Expr::IndexAssign {
|
|
3711
|
+
object,
|
|
3712
|
+
index,
|
|
3713
|
+
value,
|
|
3714
|
+
..
|
|
3715
|
+
} => {
|
|
3716
|
+
tref_expr(object, out);
|
|
3717
|
+
tref_expr(index, out);
|
|
3718
|
+
tref_expr(value, out);
|
|
3719
|
+
}
|
|
3720
|
+
Expr::TemplateLiteral { exprs, .. } => {
|
|
3721
|
+
for e in exprs {
|
|
3722
|
+
tref_expr(e, out);
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
_ => {}
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
fn tref_stmt(stmt: &Statement, out: &mut Vec<TypeOcc>) {
|
|
3730
|
+
match stmt {
|
|
3731
|
+
Statement::TypeAlias {
|
|
3732
|
+
name,
|
|
3733
|
+
name_span,
|
|
3734
|
+
ty,
|
|
3735
|
+
..
|
|
3736
|
+
} => {
|
|
3737
|
+
out.push(TypeOcc {
|
|
3738
|
+
name: name.clone(),
|
|
3739
|
+
span: *name_span,
|
|
3740
|
+
is_decl: true,
|
|
3741
|
+
});
|
|
3742
|
+
tref_ann(ty, out);
|
|
3743
|
+
}
|
|
3744
|
+
Statement::VarDecl { type_ann, init, .. } => {
|
|
3745
|
+
if let Some(t) = type_ann {
|
|
3746
|
+
tref_ann(t, out);
|
|
3747
|
+
}
|
|
3748
|
+
if let Some(e) = init {
|
|
3749
|
+
tref_expr(e, out);
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
Statement::VarDeclDestructure { init, .. } => tref_expr(init, out),
|
|
3753
|
+
Statement::DeclareVar { type_ann, .. } => {
|
|
3754
|
+
if let Some(t) = type_ann {
|
|
3755
|
+
tref_ann(t, out);
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3758
|
+
Statement::FunDecl {
|
|
3759
|
+
params,
|
|
3760
|
+
rest_param,
|
|
3761
|
+
return_type,
|
|
3762
|
+
body,
|
|
3763
|
+
..
|
|
3764
|
+
} => {
|
|
3765
|
+
for p in params {
|
|
3766
|
+
tref_param(p, out);
|
|
3767
|
+
}
|
|
3768
|
+
if let Some(rp) = rest_param {
|
|
3769
|
+
tref_typed_param(rp, out);
|
|
3770
|
+
}
|
|
3771
|
+
if let Some(t) = return_type {
|
|
3772
|
+
tref_ann(t, out);
|
|
3773
|
+
}
|
|
3774
|
+
tref_stmt(body, out);
|
|
3775
|
+
}
|
|
3776
|
+
Statement::DeclareFun {
|
|
3777
|
+
params,
|
|
3778
|
+
rest_param,
|
|
3779
|
+
return_type,
|
|
3780
|
+
..
|
|
3781
|
+
} => {
|
|
3782
|
+
for p in params {
|
|
3783
|
+
tref_param(p, out);
|
|
3784
|
+
}
|
|
3785
|
+
if let Some(rp) = rest_param {
|
|
3786
|
+
tref_typed_param(rp, out);
|
|
3787
|
+
}
|
|
3788
|
+
if let Some(t) = return_type {
|
|
3789
|
+
tref_ann(t, out);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
Statement::Block { statements, .. } | Statement::Multi { statements, .. } => {
|
|
3793
|
+
for s in statements {
|
|
3794
|
+
tref_stmt(s, out);
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
Statement::ExprStmt { expr, .. } => tref_expr(expr, out),
|
|
3798
|
+
Statement::If {
|
|
3799
|
+
cond,
|
|
3800
|
+
then_branch,
|
|
3801
|
+
else_branch,
|
|
3802
|
+
..
|
|
3803
|
+
} => {
|
|
3804
|
+
tref_expr(cond, out);
|
|
3805
|
+
tref_stmt(then_branch, out);
|
|
3806
|
+
if let Some(e) = else_branch {
|
|
3807
|
+
tref_stmt(e, out);
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
Statement::While { cond, body, .. } => {
|
|
3811
|
+
tref_expr(cond, out);
|
|
3812
|
+
tref_stmt(body, out);
|
|
3813
|
+
}
|
|
3814
|
+
Statement::For {
|
|
3815
|
+
init,
|
|
3816
|
+
cond,
|
|
3817
|
+
update,
|
|
3818
|
+
body,
|
|
3819
|
+
..
|
|
3820
|
+
} => {
|
|
3821
|
+
if let Some(i) = init {
|
|
3822
|
+
tref_stmt(i, out);
|
|
3823
|
+
}
|
|
3824
|
+
if let Some(c) = cond {
|
|
3825
|
+
tref_expr(c, out);
|
|
3826
|
+
}
|
|
3827
|
+
if let Some(u) = update {
|
|
3828
|
+
tref_expr(u, out);
|
|
3829
|
+
}
|
|
3830
|
+
tref_stmt(body, out);
|
|
3831
|
+
}
|
|
3832
|
+
Statement::ForOf { iterable, body, .. } => {
|
|
3833
|
+
tref_expr(iterable, out);
|
|
3834
|
+
tref_stmt(body, out);
|
|
3835
|
+
}
|
|
3836
|
+
Statement::DoWhile { body, cond, .. } => {
|
|
3837
|
+
tref_stmt(body, out);
|
|
3838
|
+
tref_expr(cond, out);
|
|
3839
|
+
}
|
|
3840
|
+
Statement::Return { value, .. } => {
|
|
3841
|
+
if let Some(e) = value {
|
|
3842
|
+
tref_expr(e, out);
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
Statement::Throw { value, .. } => tref_expr(value, out),
|
|
3846
|
+
Statement::Switch {
|
|
3847
|
+
expr,
|
|
3848
|
+
cases,
|
|
3849
|
+
default_body,
|
|
3850
|
+
..
|
|
3851
|
+
} => {
|
|
3852
|
+
tref_expr(expr, out);
|
|
3853
|
+
for (c, body) in cases {
|
|
3854
|
+
if let Some(c) = c {
|
|
3855
|
+
tref_expr(c, out);
|
|
3856
|
+
}
|
|
3857
|
+
for s in body {
|
|
3858
|
+
tref_stmt(s, out);
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
if let Some(body) = default_body {
|
|
3862
|
+
for s in body {
|
|
3863
|
+
tref_stmt(s, out);
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
Statement::Try {
|
|
3868
|
+
body,
|
|
3869
|
+
catch_body,
|
|
3870
|
+
finally_body,
|
|
3871
|
+
..
|
|
3872
|
+
} => {
|
|
3873
|
+
tref_stmt(body, out);
|
|
3874
|
+
if let Some(c) = catch_body {
|
|
3875
|
+
tref_stmt(c, out);
|
|
3876
|
+
}
|
|
3877
|
+
if let Some(f) = finally_body {
|
|
3878
|
+
tref_stmt(f, out);
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
3882
|
+
ExportDeclaration::Named(inner) => tref_stmt(inner, out),
|
|
3883
|
+
ExportDeclaration::Default(e) => tref_expr(e, out),
|
|
3884
|
+
},
|
|
3885
|
+
_ => {}
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
/// If the cursor sits on a type-alias declaration name or on a `: T` type-reference use, returns
|
|
3890
|
+
/// every source span that must be renamed together (the declaration plus all annotation uses).
|
|
3891
|
+
/// Returns `None` when the cursor is not on a user-declared type alias, so callers fall back to
|
|
3892
|
+
/// value-binding rename. Fixes the silent breakage where renaming a `type T` left every `: T`
|
|
3893
|
+
/// annotation stale (type names are in a namespace the value resolver does not see).
|
|
3894
|
+
pub fn type_alias_rename_spans(
|
|
3895
|
+
program: &Program,
|
|
3896
|
+
source: &str,
|
|
3897
|
+
lsp_line: u32,
|
|
3898
|
+
lsp_character: u32,
|
|
3899
|
+
) -> Option<Vec<tishlang_ast::Span>> {
|
|
3900
|
+
let mut occ: Vec<TypeOcc> = Vec::new();
|
|
3901
|
+
for s in &program.statements {
|
|
3902
|
+
tref_stmt(s, &mut occ);
|
|
3903
|
+
}
|
|
3904
|
+
// The type name whose declaration or use span contains the cursor.
|
|
3905
|
+
let target = occ
|
|
3906
|
+
.iter()
|
|
3907
|
+
.find(|o| pos::span_contains_lsp_position(source, &o.span, lsp_line, lsp_character))?
|
|
3908
|
+
.name
|
|
3909
|
+
.clone();
|
|
3910
|
+
// Only rename user-declared aliases — never a builtin (`number`, `string`, …) used in an
|
|
3911
|
+
// annotation, which has no declaration to keep in sync.
|
|
3912
|
+
if !occ.iter().any(|o| o.is_decl && o.name == target) {
|
|
3913
|
+
return None;
|
|
3914
|
+
}
|
|
3915
|
+
let mut spans: Vec<tishlang_ast::Span> = occ
|
|
3916
|
+
.iter()
|
|
3917
|
+
.filter(|o| o.name == target)
|
|
3918
|
+
.map(|o| o.span)
|
|
3919
|
+
.collect();
|
|
3920
|
+
spans.sort_by_key(|s| (s.start.0, s.start.1, s.end.0, s.end.1));
|
|
3921
|
+
spans.dedup();
|
|
3922
|
+
Some(spans)
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3573
3925
|
pub fn reference_spans_for_def(
|
|
3574
3926
|
program: &Program,
|
|
3575
3927
|
source: &str,
|
|
@@ -4329,4 +4681,32 @@ mod tests {
|
|
|
4329
4681
|
assert_eq!(ch.members[0].as_ref(), "b");
|
|
4330
4682
|
assert_eq!(ch.members[1].as_ref(), "c");
|
|
4331
4683
|
}
|
|
4684
|
+
|
|
4685
|
+
#[test]
|
|
4686
|
+
fn type_alias_rename_collects_decl_and_all_annotation_uses() {
|
|
4687
|
+
// #131: renaming a type alias must edit the declaration AND every `: T` annotation, or the
|
|
4688
|
+
// program silently breaks. Type names are in a namespace the value resolver doesn't see.
|
|
4689
|
+
let src = "type T = number\nlet a: T = 1\nfn f(x: T): T { return x }\n";
|
|
4690
|
+
let program = parse(src).expect("parse");
|
|
4691
|
+
fn span_text(src: &str, sp: &tishlang_ast::Span) -> String {
|
|
4692
|
+
let lines: Vec<&str> = src.lines().collect();
|
|
4693
|
+
let (sl, sc) = sp.start;
|
|
4694
|
+
let (_el, ec) = sp.end;
|
|
4695
|
+
lines[sl - 1].chars().skip(sc - 1).take(ec - sc).collect()
|
|
4696
|
+
}
|
|
4697
|
+
// Cursor on the declaration name `T` (0-indexed line 0, char 5).
|
|
4698
|
+
let spans = type_alias_rename_spans(&program, src, 0, 5).expect("type rename spans");
|
|
4699
|
+
assert_eq!(spans.len(), 4, "decl + let-ann + param-ann + return-ann; spans={spans:?}");
|
|
4700
|
+
for sp in &spans {
|
|
4701
|
+
assert_eq!(span_text(src, sp), "T", "every rename span covers the `T` token; {sp:?}");
|
|
4702
|
+
}
|
|
4703
|
+
// Initiating from a `: T` use also works (the param annotation, line 2 char 8).
|
|
4704
|
+
let from_use = type_alias_rename_spans(&program, src, 2, 8).expect("rename from use");
|
|
4705
|
+
assert_eq!(from_use.len(), 4, "same set whether initiated from decl or use");
|
|
4706
|
+
// A builtin used in an annotation (`number`, line 0 char 9) is not a user alias -> None.
|
|
4707
|
+
assert!(
|
|
4708
|
+
type_alias_rename_spans(&program, src, 0, 9).is_none(),
|
|
4709
|
+
"builtin `number` has no declaration to rename"
|
|
4710
|
+
);
|
|
4711
|
+
}
|
|
4332
4712
|
}
|
package/package.json
CHANGED
|
Binary file
|
package/platform/darwin-x64/tish
CHANGED
|
Binary file
|
|
Binary file
|
package/platform/linux-x64/tish
CHANGED
|
Binary file
|
|
Binary file
|