> For the complete documentation index, see [llms.txt](https://code-visualization.shawnxie.top/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://code-visualization.shawnxie.top/di-er-pian-yuan-ma-jie-gou-hua-yuan-li/symbols-scopes-types.md).

# 符号表、作用域与类型关系

AST 解决了代码结构问题，但很多代码理解问题还需要语义信息。比如一个变量引用到底指向哪个定义，一个方法调用实际属于哪个类，一个接口有哪些实现。这些问题不能只靠树结构回答，需要符号表、作用域和类型关系。

如果说 AST 让工具知道“这里有一个名字”，符号和类型系统则让工具知道“这个名字指向谁”。这一层是 IDE 跳转、引用查找、调用图、自动重构和影响面分析的核心。

## 什么是符号

符号可以理解为程序中有名字的实体。常见符号包括：

* 包、模块、命名空间。
* 类、接口、枚举、结构体。
* 方法、函数、构造函数。
* 字段、变量、常量、参数。
* 类型别名、泛型参数。

符号表记录这些实体的声明位置、类型、可见范围和其他属性。它把“文本里的名字”提升为“程序里的实体”。

例如：

```java
class OrderService {
    private final OrderRepository repository;

    void create(Order order) {
        repository.save(order);
    }
}
```

这里至少包含 `OrderService`、`OrderRepository`、`repository`、`create`、`Order`、`order`、`save` 等符号或符号引用。AST 能识别它们的位置，符号解析需要进一步判断每个名字对应哪个定义。

## 定义与引用

代码理解中最基础的一类边是“定义 - 引用”关系。

* 定义：符号在哪里声明。
* 引用：符号在哪里被使用。
* 定义到引用：这个声明影响哪些使用位置。
* 引用到定义：这个使用位置来自哪个声明。

IDE 的“跳转到定义”和“查找所有引用”，本质上就是在定义和引用之间导航。

这类关系对代码可视化很重要。比如一个字段被多个模块读写时，定义引用图能帮助判断修改字段类型会影响哪些位置。一个公共方法被很多调用方引用时，引用关系也是影响面分析的基础。

## 作用域：名字在哪里有效

作用域决定一个符号在代码中的可见范围。没有作用域解析，工具很容易把名字相同但语义不同的实体混在一起。

例如：

```java
class Example {
    private User user;

    void update(User user) {
        this.user = user;
    }
}
```

这里有两个 `user`：一个是字段，一个是方法参数。`this.user` 指向字段，右侧 `user` 指向参数。字符串搜索无法区分，AST 只能看到名字，作用域解析才能判断引用目标。

常见作用域包括：

* 全局作用域。
* 模块或包作用域。
* 类作用域。
* 方法作用域。
* 块级作用域。
* 闭包或函数作用域。

不同语言的规则差异很大。JavaScript 的 `var`、`let`、闭包和模块系统，与 Java 的类和方法作用域就不一样。因此，符号解析通常需要语言专用工具。

## 类型解析：知道值是什么

类型信息回答的是“这个表达式是什么类型”。在静态类型语言中，类型通常可以从声明、泛型、继承和类型推断中获得。在动态语言中，类型可能需要运行时信息、类型注解或测试执行来辅助推断。

类型解析可以支持：

* 判断字段和变量的类型。
* 判断一个方法调用属于哪个类。
* 判断接口有哪些实现。
* 判断重载方法的具体目标。
* 判断泛型类型参数的实际约束。

类型解析对调用图非常关键。没有类型信息，`service.create()` 只能得到一个方法名；有了类型信息，工具才能把它连接到 `OrderService.create` 或其他具体实现。

## 继承、实现、重载与重写

面向对象语言中，类型关系会让调用分析变得更复杂。

接口和实现关系意味着同一个接口方法可能有多个实现。继承和重写意味着父类引用可能在运行时调用子类方法。重载意味着同名方法可能根据参数类型解析到不同目标。

例如：

```java
interface PaymentService {
    void pay(Order order);
}

class WechatPaymentService implements PaymentService {
    public void pay(Order order) {}
}

class AliPaymentService implements PaymentService {
    public void pay(Order order) {}
}
```

如果代码里出现：

```java
paymentService.pay(order);
```

静态分析可能只能知道它调用的是 `PaymentService.pay`，但具体实现取决于依赖注入配置、运行时条件或业务参数。一个严谨的调用图需要表达这种不确定性：这是一个接口调用，可能分派到多个实现。

## 符号关系如何进入图谱

符号、作用域和类型关系可以转化为图谱中的节点和边。

节点可以是类、接口、方法、字段、变量、参数。边可以是：

* `defines`：文件或类定义某个符号。
* `references`：某个位置引用某个符号。
* `extends`：类继承父类。
* `implements`：类实现接口。
* `overrides`：方法重写父类或接口方法。
* `calls`：方法调用另一个方法。
* `typed_as`：变量或表达式拥有某种类型。

这些边让代码图谱不只是结构树，而是语义网络。后续的影响面分析、架构治理和 Agent 查询都会依赖这些边。

## IDE 能力背后的原理

现代 IDE 和 Language Server 提供的很多能力都建立在符号和类型解析之上：

* 跳转到定义。
* 查找引用。
* 自动补全。
* 重命名重构。
* 查找实现。
* 类型提示。
* 错误诊断。

代码可视化系统可以复用这些能力。例如通过 Language Server Protocol 获取符号、定义和引用，或者使用语言生态中的编译器 API 提取类型信息。

这也是工程实践中的一个重要选择：如果语言生态已经有成熟 Language Server 或编译器 API，优先复用它们通常比从零实现解析器更稳。

## 常见误差和边界

符号和类型解析也不是总能给出唯一答案。常见困难包括：

* 反射和动态加载。
* 依赖注入和运行时绑定。
* 多态分派。
* 泛型擦除或复杂类型推断。
* 动态语言运行时修改对象结构。
* 缺失依赖或生成代码未纳入分析。

因此，代码可视化结果需要区分“确定关系”和“候选关系”。例如调用图可以标记某条边来自静态类型解析，另一条边来自框架规则推断，另一条边来自运行时 Trace 确认。

## 和 AI Agent 的关系

AI Agent 需要知道名字和关系，而不是只看文本。修改一个方法前，它最好能查询：

* 这个方法在哪里定义。
* 哪些地方引用了它。
* 它属于哪个类型。
* 它重写或实现了哪个接口方法。
* 它可能影响哪些实现类或调用方。

这些查询都依赖符号表、作用域和类型关系。如果没有这一层，Agent 容易把同名方法混淆，或者只修改一个实现却遗漏接口和测试。

## 小结

AST 让工具看见代码结构，符号表、作用域和类型关系让工具理解代码语义。定义引用、类型解析、继承实现和方法分派，是调用图、影响面分析和自动重构的基础。

下一章会继续从语义结构走向程序行为：当我们想知道代码可能如何执行、数据如何传播时，就需要 IR、SSA、CFG 和 DFG。
