@tishlang/tish 2.2.4 → 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 +111 -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)),
|
|
@@ -119,6 +124,15 @@ fn document_symbol(
|
|
|
119
124
|
selection_range: Range,
|
|
120
125
|
children: Option<Vec<DocumentSymbol>>,
|
|
121
126
|
) -> DocumentSymbol {
|
|
127
|
+
// LSP spec: selectionRange must be contained in range, or VS Code rejects the whole document
|
|
128
|
+
// outline ("selectionRange must be contained in fullRange"). Some declaration spans are
|
|
129
|
+
// unset/degenerate (e.g. a top-level VarDecl's span defaults to an empty range) and so do not
|
|
130
|
+
// enclose the name span; fall back to the name range to keep the invariant.
|
|
131
|
+
let contains = (range.start.line, range.start.character)
|
|
132
|
+
<= (selection_range.start.line, selection_range.start.character)
|
|
133
|
+
&& (selection_range.end.line, selection_range.end.character)
|
|
134
|
+
<= (range.end.line, range.end.character);
|
|
135
|
+
let range = if contains { range } else { selection_range };
|
|
122
136
|
DocumentSymbol {
|
|
123
137
|
name,
|
|
124
138
|
detail,
|
|
@@ -301,12 +315,35 @@ impl LanguageServer for Backend {
|
|
|
301
315
|
.write()
|
|
302
316
|
.unwrap()
|
|
303
317
|
.insert(uri.clone(), chg.text.clone());
|
|
304
|
-
|
|
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
|
+
});
|
|
305
341
|
}
|
|
306
342
|
}
|
|
307
343
|
|
|
308
344
|
async fn did_close(&self, p: DidCloseTextDocumentParams) {
|
|
309
345
|
self.docs.write().unwrap().remove(&p.text_document.uri);
|
|
346
|
+
self.edit_seq.write().unwrap().remove(&p.text_document.uri);
|
|
310
347
|
self.client
|
|
311
348
|
.publish_diagnostics(p.text_document.uri, vec![], None)
|
|
312
349
|
.await;
|
|
@@ -691,6 +728,30 @@ impl LanguageServer for Backend {
|
|
|
691
728
|
let Ok(program) = tishlang_parser::parse(&text) else {
|
|
692
729
|
return Ok(None);
|
|
693
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
|
+
}
|
|
694
755
|
let Some(def) = tishlang_resolve::definition_span(&program, &text, pos.line, pos.character)
|
|
695
756
|
else {
|
|
696
757
|
return Ok(None);
|
|
@@ -1065,7 +1126,7 @@ fn is_ident_char(c: char) -> bool {
|
|
|
1065
1126
|
fn render_type(t: &tishlang_ast::TypeAnnotation) -> String {
|
|
1066
1127
|
use tishlang_ast::{TypeAnnotation as T, TypeLiteral as L};
|
|
1067
1128
|
match t {
|
|
1068
|
-
T::Simple(s) => s.to_string(),
|
|
1129
|
+
T::Simple(s, _) => s.to_string(),
|
|
1069
1130
|
T::Array(inner) => {
|
|
1070
1131
|
// Parenthesize composite element types so `(A | B)[]` reads unambiguously.
|
|
1071
1132
|
if matches!(
|
|
@@ -1119,7 +1180,7 @@ fn shallow_expr_type(e: &tishlang_ast::Expr) -> Option<tishlang_ast::TypeAnnotat
|
|
|
1119
1180
|
Literal::Bool(_) => "boolean",
|
|
1120
1181
|
Literal::Null => "null",
|
|
1121
1182
|
};
|
|
1122
|
-
Some(T::Simple(Arc::from(name)))
|
|
1183
|
+
Some(T::Simple(Arc::from(name), tishlang_ast::Span::default()))
|
|
1123
1184
|
} else {
|
|
1124
1185
|
None
|
|
1125
1186
|
}
|
|
@@ -1652,11 +1713,11 @@ mod hover_tests {
|
|
|
1652
1713
|
#[test]
|
|
1653
1714
|
fn composite_types_render() {
|
|
1654
1715
|
use tishlang_ast::{TypeAnnotation as T, TypeLiteral as L};
|
|
1655
|
-
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())));
|
|
1656
1717
|
assert_eq!(render_type(&arr), "number[]");
|
|
1657
|
-
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())]);
|
|
1658
1719
|
assert_eq!(render_type(&tup), "[number, string]");
|
|
1659
|
-
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())]);
|
|
1660
1721
|
assert_eq!(render_type(&uni), "number | null");
|
|
1661
1722
|
assert_eq!(render_type(&T::Literal(L::Str("on".into()))), "\"on\"");
|
|
1662
1723
|
let arr_of_union = T::Array(Box::new(uni));
|
|
@@ -1671,6 +1732,50 @@ mod hover_tests {
|
|
|
1671
1732
|
assert_eq!(full_doc_end(""), (0, 0));
|
|
1672
1733
|
assert_eq!(full_doc_end("café"), (0, 4)); // UTF-16 units (é = 1), not bytes (5)
|
|
1673
1734
|
}
|
|
1735
|
+
|
|
1736
|
+
#[test]
|
|
1737
|
+
fn doc_symbols_satisfy_lsp_selection_containment() {
|
|
1738
|
+
// LSP requires every DocumentSymbol's selectionRange ⊆ range, or VS Code rejects the
|
|
1739
|
+
// whole outline ("selectionRange must be contained in fullRange"). Exercise the
|
|
1740
|
+
// declaration forms the outline emits.
|
|
1741
|
+
use tower_lsp::lsp_types::DocumentSymbol;
|
|
1742
|
+
fn check(syms: &[DocumentSymbol], src: &str) {
|
|
1743
|
+
for s in syms {
|
|
1744
|
+
let (r, sel) = (&s.range, &s.selection_range);
|
|
1745
|
+
let contained = (r.start.line, r.start.character)
|
|
1746
|
+
<= (sel.start.line, sel.start.character)
|
|
1747
|
+
&& (sel.end.line, sel.end.character) <= (r.end.line, r.end.character);
|
|
1748
|
+
assert!(
|
|
1749
|
+
contained,
|
|
1750
|
+
"selectionRange {sel:?} not contained in range {r:?} for `{}` in:\n{src}",
|
|
1751
|
+
s.name
|
|
1752
|
+
);
|
|
1753
|
+
if let Some(children) = &s.children {
|
|
1754
|
+
check(children, src);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
let sources = [
|
|
1759
|
+
"fn f(x) { return x }\n",
|
|
1760
|
+
"let a = 1\n",
|
|
1761
|
+
"let a = 1, b = 2\n",
|
|
1762
|
+
"export fn g() { return 1 }\n",
|
|
1763
|
+
"export let x = 1\n",
|
|
1764
|
+
"type T = number\n",
|
|
1765
|
+
"declare fn h(): void\n",
|
|
1766
|
+
"declare let y: number\n",
|
|
1767
|
+
"fn outer() {\n fn inner() { return 1 }\n return inner\n}\n",
|
|
1768
|
+
"export type Opts = { a: number }\n",
|
|
1769
|
+
];
|
|
1770
|
+
for src in sources {
|
|
1771
|
+
let p = parse(src);
|
|
1772
|
+
let mut syms = Vec::new();
|
|
1773
|
+
for s in &p.statements {
|
|
1774
|
+
doc_symbol_stmt(s, src, &mut syms);
|
|
1775
|
+
}
|
|
1776
|
+
check(&syms, src);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1674
1779
|
}
|
|
1675
1780
|
|
|
1676
1781
|
#[cfg(test)]
|
|
@@ -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
|