microsoft / microsoft/TypeScript

Proposal: `unstable` and `volatile` control-flow analysis modifiers

未关闭
#63,681 2 条评论 1 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

Awaiting More Feedback Suggestion
主要语言
Go
星标
111k
派生
14.3k
平均合并
2 天 4 小时
30 天内合并 PR
132

描述

### 🔍 Search Terms

CFA narrowing optimistic pessimistic unstable volatile

### ✅ Viability Checklist

- [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [x] This wouldn't change the runtime behavior of existing JavaScript code
- [x] This could be implemented without emitting different JS based on the types of the expressions
- [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- [x] This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- [x] This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

### ⭐ Suggestion

Add two erasable declaration modifiers that let authors opt out of TypeScript's optimistic control-flow assumptions:

- `unstable`: narrowing is discarded after opaque execution, such as a function call, `await`, or `yield`.
- `volatile`: every read is a fresh observation, so narrowing cannot be reused by a later read.

`volatile` is the stronger mode and subsumes `unstable`.

These modifiers affect only control-flow analysis. They do not change types, participate in assignability, or emit JavaScript.

### 📃 Motivating Example

#9998 established that neither globally optimistic nor globally pessimistic CFA works well. TypeScript chose optimism, but some programs, especially parsers and state machines, need pessimism for selected declarations.

```ts
unstable let token: SyntaxKind;

if (token === SyntaxKind.ExportKeyword) {
nextToken();

// The previous narrowing was discarded.
if (token === SyntaxKind.DefaultKeyword) {
// ...
}
}
```

#57725 shows the complementary problem: TypeScript treats repeated property reads as stable even when a getter computes a new value each time.

```ts
const model = {
volatile get value(): string | undefined {
return Math.random() > 0.5 ? "Hello" : undefined;
}
};

if (model.value) {
model.value.toLowerCase();
// Error: the second read may produce undefined.
}
```

The value can be explicitly stabilized:

```ts
const value = model.value;

if (value) {
value.toLowerCase();
}
```

### 💻 Use Cases

1. What do you want to use this for? To explicitly control control-flow analysis for declarations whose values are not stable. This is particularly useful for parser state, state machines, live views over mutable state, and computed getters whose values may change between observations.

2. What shortcomings exist with current approaches? Today, the only way to influence CFA is indirectly through API shape (property vs. function) or by restructuring code. This relies on convention rather than explicit semantics and does not provide an escape hatch when the default heuristics are incorrect.

3. What workarounds are you using in the meantime? For `unstable`, I avoid relying on narrowing across function calls, or wrap mutable state behind trivial getters to defeat CFA. For `volatile`, the only practical workaround is to redesign the API by exposing a method instead of a getter, even when a property is the more natural abstraction.

### Declaration forms

Both modifiers should be accepted on the same forms:

```ts
unstable let current: string | undefined;
volatile let externalValue: string | undefined;

interface State {
unstable token: SyntaxKind;
volatile readonly temperature: number | undefined;
}

class Model {
unstable value: string | undefined;

volatile get currentTime(): number {
return Date.now();
}
}
```

They should also be supported in object types, interfaces, parameters, ambient declarations, and declaration files.

The modifiers are flow metadata only:

```ts
type Stable = { value: string | undefined };
type Volatile = { volatile value: string | undefined };
```

`Stable` and `Volatile` remain structurally assignable in both directions.

### Defaults and scope

Existing behavior remains the default:

- variables and properties are treated as stable;
- calls are treated as fresh.

This proposal only adds opt-outs when those defaults are incorrect. It does not add callable purity annotations, narrow repeated function calls, infer effects, or describe setter postconditions.

The naming is inspired by PostgreSQL's explicit [`IMMUTABLE`](https://www.postgresql.org/docs/current/sql-createfunction.html)[,](https://www.postgresql.org/docs/current/sql-createfunction.html)[`STABLE`](https://www.postgresql.org/docs/current/sql-createfunction.html)[, and](https://www.postgresql.org/docs/current/sql-createfunction.html)[`VOLATILE`](https://www.postgresql.org/docs/current/sql-createfunction.html) optimizer contracts, but the proposed semantics are specific to TypeScript CFA.

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

首先阅读此提案以及所引用的 issue #9998 和 #57725,以了解它针对的控制流分析问题。在列出的上下文中定义并实现 `unstable` 和 `volatile` 声明形式,同时保持结构赋值兼容性和 JavaScript 输出;当支持所提议的 narrowing 行为且不改变运行时生成结果时,即表示完成。

由索引模型根据 Issue 内容生成。

评估

技术栈
typescript
领域
compilers
Issue 类型
功能
难度
5/5
预计耗时
一周以上
活跃度
活跃
描述清晰度
基本清楚
新手友好度
35/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。