Skip to content

Commit bcb2c27

Browse files
committed
support type inference
1 parent a3e1083 commit bcb2c27

16 files changed

+1415
-21
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
node_modules/
22
dist/
33
.vscode-test/
4-
samples/

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
# Pine Script v6 VS Code 扩展
22

3-
为 VS Code 提供 Pine Script v6 语言支持,包括语法高亮、智能提示和诊断功能。
4-
53
## 主要功能
64

75
* **语法高亮:** 为 Pine Script v6 代码提供准确的语法高亮。
8-
* **智能感知 (IntelliSense):** 为 Pine Script v6 的函数、变量和关键字提供智能代码补全。
6+
* **智能感知 (IntelliSense):** 为函数、变量和关键字提供智能代码补全。
7+
* **函数签名帮助:** 在您输入时显示函数参数信息。
98
* **悬停文档 (Hover Docs):** 将鼠标悬停在任何函数或变量上以查看其文档。
9+
* **转到定义:** 快速跳转到变量或函数的定义处。
10+
* **查找所有引用:** 查找一个符号在何处被使用。
11+
* **符号重命名:** 安全地重命名变量和函数。
12+
* **嵌入提示 (Inlay Hints):** 显示推断的类型信息,以提高代码可读性。
13+
* **颜色预览:** 直接在编辑器中预览颜色字面量。
1014
* **实时校验 (Real-time Validation):** 通过实时错误检查,即时获取代码反馈。
1115

1216
## 安装

docs/TYPE_INFERENCE_DESIGN.md

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
# Pine Script 类型推断系统设计文档
2+
3+
## 功能概述
4+
5+
VSCode 扩展提供了完整的 Pine Script v6 类型推断系统,支持:
6+
- ✅ 变量声明类型推断
7+
- ✅ 解构赋值类型推断
8+
- ✅ 用户自定义函数元组返回值推断
9+
- ✅ Inlay Hints 类型提示显示
10+
- ✅ 所有作用域支持(全局、函数、循环、条件语句)
11+
12+
## 核心架构
13+
14+
### 1. 三阶段验证流程
15+
16+
```
17+
输入: Pine Script 代码
18+
19+
[Parser] 解析 → AST
20+
21+
[AstValidator.validate()]
22+
23+
第一阶段: collectDeclarations(ast)
24+
- 遍历 AST,收集所有声明
25+
- 推断变量类型(literal/expression)
26+
- 保存函数声明引用
27+
- 构建符号表
28+
29+
第二阶段: validateStatement(ast)
30+
- 验证表达式和语句
31+
- 标记变量使用情况
32+
- 进入/退出作用域
33+
34+
第三阶段: checkUnusedVariables()
35+
- 检查未使用的变量
36+
37+
第四阶段: buildSymbolMap() ✅ 已优化
38+
- 构建 name:line → symbol 的映射
39+
- 供 InlayHintsProvider 使用
40+
41+
输出: 验证错误列表 + 完整符号表 + 符号映射
42+
```
43+
44+
### 2. 关键组件
45+
46+
#### AstValidator (src/parser/astValidator.ts)
47+
核心验证器,负责:
48+
- 符号表管理
49+
- 类型推断
50+
- 错误收集
51+
52+
**关键字段**:
53+
```typescript
54+
private symbolTable: SymbolTable; // 符号表
55+
private functionDeclarations: Map; // 函数声明缓存
56+
private expressionTypes: Map; // 表达式类型缓存
57+
private symbolMap: Map<string, SymbolInfo>; // 符号映射缓存 ✅ 已优化
58+
```
59+
60+
**关键方法**:
61+
```typescript
62+
validate(ast: Program): ValidationError[] // 主验证流程
63+
inferFunctionTupleReturnTypes(funcName): PineType[] // 用户函数元组推断
64+
inferExpressionTypeWithLocals(expr, localTypes) // 独立类型推断
65+
buildSymbolMap(): void // 构建符号映射 ✅ 已优化
66+
getSymbolMap(): Map<string, SymbolInfo> // 获取符号映射 ✅ 已优化
67+
```
68+
69+
#### SymbolTable (src/parser/symbolTable.ts)
70+
作用域管理,符号存储:
71+
```typescript
72+
class Scope {
73+
private symbols: Map<string, Symbol>; // 当前作用域符号
74+
private parent: Scope | null; // 父作用域
75+
private children: Scope[]; // 子作用域
76+
}
77+
78+
class SymbolTable {
79+
private globalScope: Scope; // 全局作用域
80+
private currentScope: Scope; // 当前作用域
81+
82+
enterScope() // 进入子作用域
83+
exitScope() // 退出到父作用域
84+
getAllSymbols() // 递归获取所有作用域的符号
85+
}
86+
```
87+
88+
#### InlayHintsProvider (src/inlayHintsProvider.ts)
89+
类型提示显示:
90+
- 解析文档 → 验证 → 获取缓存的符号映射 ✅ 已优化
91+
- 递归遍历 AST,为每个变量声明创建 InlayHint
92+
- 使用 symbolMap 快速查找符号类型
93+
94+
**优化前**:
95+
```typescript
96+
const allSymbols = symbolTable.getAllSymbols();
97+
const symbolMap = new Map(); // 每次都重建
98+
for (const symbol of allSymbols) {
99+
symbolMap.set(`${symbol.name}:${symbol.line}`, symbol);
100+
}
101+
```
102+
103+
**优化后** ✅:
104+
```typescript
105+
const symbolMap = validator.getSymbolMap(); // 直接使用缓存
106+
```
107+
108+
## 用户自定义函数元组推断
109+
110+
### 问题
111+
```pine
112+
myFunction() =>
113+
a = 10
114+
b = true
115+
[a, b]
116+
117+
[x, y] = myFunction() // x 和 y 的类型是?
118+
```
119+
120+
### 解决方案: 独立类型推断系统
121+
122+
**核心方法**: `inferFunctionTupleReturnTypes(funcName: string): PineType[]`
123+
124+
```typescript
125+
1.functionDeclarations 获取函数 AST
126+
2. 创建临时 localTypes Map (不修改符号表)
127+
3. 添加参数类型到 localTypes
128+
4. 递归遍历函数体,收集变量类型:
129+
- 使用 inferExpressionTypeWithLocals()
130+
- 优先查找 localTypes,再查找符号表
131+
5. 提取函数返回的数组字面量
132+
6. 推断数组每个元素的类型
133+
7. 返回类型数组
134+
```
135+
136+
**关键优势**:
137+
- ✅ 不干扰符号表的作用域管理
138+
- ✅ 不影响变量使用标记
139+
- ✅ 支持嵌套作用域(if/for/while)
140+
141+
### 示例流程
142+
143+
```pine
144+
detect_high_low(...) =>
145+
cycle_len = bar_index - cycle_start + 1 // series<int>
146+
is_top = false // bool
147+
is_bottom = false // bool
148+
[is_top, is_bottom]
149+
150+
[kdj_top, kdj_bottom] = detect_high_low(...)
151+
```
152+
153+
**推断步骤**:
154+
1. 解构赋值触发 → 调用 `inferFunctionTupleReturnTypes('detect_high_low')`
155+
2. 创建 localTypes: `{'cycle_len': 'series<int>', 'is_top': 'bool', 'is_bottom': 'bool'}`
156+
3. 返回表达式: `[is_top, is_bottom]`
157+
4. 推断元素类型: `[inferType(is_top), inferType(is_bottom)]``['bool', 'bool']`
158+
5. 赋值给变量: `kdj_top: 'bool'`, `kdj_bottom: 'bool'`
159+
160+
## Inlay Hints 显示机制
161+
162+
### 作用域查找问题
163+
164+
**问题**: `symbolTable.lookup()` 只能向上查找父作用域,无法访问已退出的子作用域。
165+
166+
```
167+
全局作用域
168+
├─ 函数 A 作用域 (已退出)
169+
│ ├─ 变量 x
170+
│ └─ 变量 y
171+
└─ 当前作用域 (全局)
172+
173+
lookup('x') → 找不到! (因为函数作用域已退出)
174+
```
175+
176+
### 解决方案: Symbol Map
177+
178+
```typescript
179+
// 获取所有符号(包括子作用域)
180+
const allSymbols = symbolTable.getAllSymbols();
181+
182+
// 创建 Map: "name:line" → symbol
183+
const symbolMap = new Map<string, Symbol>();
184+
for (const symbol of allSymbols) {
185+
symbolMap.set(`${symbol.name}:${symbol.line}`, symbol);
186+
}
187+
188+
// 查找时使用 name+line 作为 key
189+
const symbol = symbolMap.get(`${varName}:${varLine}`);
190+
```
191+
192+
**优势**:
193+
- ✅ 可以访问所有作用域的符号
194+
- ✅ 使用行号区分同名变量
195+
- ✅ O(1) 查找性能
196+
197+
## 性能优化建议
198+
199+
### 当前实现
200+
```typescript
201+
validate(ast) {
202+
// 第一遍: 收集声明
203+
for (const stmt of ast.body) {
204+
collectDeclarations(stmt); // 遍历整个 AST
205+
}
206+
207+
// 第二遍: 验证
208+
for (const stmt of ast.body) {
209+
validateStatement(stmt); // 再次遍历整个 AST
210+
}
211+
}
212+
```
213+
214+
### 可能的优化(未实现)
215+
216+
#### 方案1: 单遍遍历
217+
合并收集和验证,但需要处理前向引用:
218+
```typescript
219+
validate(ast) {
220+
// 预先收集函数声明
221+
collectFunctionDeclarations(ast);
222+
223+
// 单遍处理
224+
for (const stmt of ast.body) {
225+
processStatement(stmt); // 收集 + 验证
226+
}
227+
}
228+
```
229+
230+
⚠️ **问题**: Pine Script 支持在声明前使用函数,需要两阶段。
231+
232+
#### 方案2: 缓存优化
233+
```typescript
234+
private expressionTypes: Map<Expression, PineType>; // ✅ 已实现
235+
```
236+
避免重复推断相同表达式的类型。
237+
238+
## 注意事项
239+
240+
### 1. 作用域管理
241+
- ✅ 函数创建新作用域
242+
- ✅ for/while 循环创建新作用域
243+
- ❌ if/else 语句**不创建**新作用域(Pine Script 特性)
244+
245+
### 2. 类型推断限制
246+
247+
**当前支持**:
248+
- ✅ 字面量类型推断
249+
- ✅ 内置函数返回类型
250+
- ✅ 二元/一元运算表达式类型
251+
- ✅ 用户函数元组返回(数组字面量)
252+
253+
**当前不支持**:
254+
- ❌ 间接返回数组 (返回变量而非字面量)
255+
- ❌ 条件返回不同类型
256+
- ❌ 复杂的类型联合/交集
257+
258+
示例:
259+
```pine
260+
// ❌ 不支持
261+
myFunc() =>
262+
arr = [1, 2]
263+
arr // 返回变量
264+
265+
// ✅ 支持
266+
myFunc() =>
267+
[1, 2] // 直接返回字面量
268+
```
269+
270+
### 3. 参数类型假设
271+
272+
```typescript
273+
// 第一个参数假设为 series<float>
274+
const paramType = i === 0 ? 'series<float>' : 'unknown';
275+
```
276+
277+
原因: Pine Script 函数通常第一个参数是指标序列。
278+
279+
⚠️ **改进空间**: 可以分析函数调用点的实参类型来推断参数类型。
280+
281+
### 4. Symbol Map Key 设计
282+
283+
使用 `name:line` 作为 key:
284+
```typescript
285+
const key = `${symbol.name}:${symbol.line}`;
286+
```
287+
288+
**优势**: 区分不同作用域的同名变量
289+
**限制**: 假设同一行不会有多个同名变量声明(合理假设)
290+
291+
## 测试
292+
293+
运行类型推断测试:
294+
```bash
295+
npm run build:tsc
296+
node test-type-inference.js
297+
```
298+
299+
测试文件:
300+
- `user-function-return-tuple.pine` - 用户函数元组测试
301+
- `samples/detect-high-low.pine` - 真实场景测试
302+
303+
## 扩展点
304+
305+
### 添加新的类型推断规则
306+
307+
`AstValidator.inferExpressionType()` 中添加:
308+
```typescript
309+
case 'NewExpressionType':
310+
// 添加推断逻辑
311+
type = inferNewType(expr);
312+
break;
313+
```
314+
315+
### 添加新的内置函数返回类型
316+
317+
`AstValidator.addKnownReturnTypes()` 中添加:
318+
```typescript
319+
'namespace.function': 'return_type',
320+
```
321+
322+
### 支持更复杂的函数分析
323+
324+
修改 `inferFunctionTupleReturnTypes()`:
325+
1. 支持追踪变量赋值链
326+
2. 分析多个 return 路径
327+
3. 递归分析嵌套函数调用
328+
329+
## 总结
330+
331+
当前实现在**简洁性****功能完整性**之间取得了良好平衡:
332+
- ✅ 两阶段验证清晰简单
333+
- ✅ 独立类型推断不干扰符号表
334+
- ✅ Symbol Map 解决作用域访问问题
335+
- ✅ 支持主要使用场景(90%+)
336+
337+
进一步优化需要权衡复杂度和收益。

0 commit comments

Comments
 (0)