microsoft / microsoft/TypeScript

(Proposal) more-sophisticated CFA, and Narrowing Boolean Types

Đang mở
#64,047 2 bình luận 0 reaction 0 người được giao Xem trên GitHub
Suggestion
Ngôn ngữ chính
Go
Star
111k
Fork
14.3k
Merge trung bình
2 ngày 4 giờ
Pull request đã merge (30 ngày)
132

Mô tả

### 🔍 Search Terms

more advanced sophisticated CFA control flow analysis inferred working across assignments variables `const`s spreads narrowing constraining types

ambient external term narrowing constraining is predicate asserts assertion boolean type types

discriminant discriminated union intersection type types literal field prop property fields props properties

### ✅ 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

**A demand for more-sophisticated CFA, working across indirections, eg variables, assignments, and spreads (see below).**

**Sophisticated boolean types ` is T` (Type-Predicate(ing) Boolean Type(s) ) to be inferred in, and permitted as, signatures of boolean variables eg `const fetching: /* boolean */ c is FetchAction`.**

### 📃 Motivating Example

```typescript
interface Action {
readonly cls: string & { __c?: any }
}

/** 'Create, Read, Update, Delete'. */
interface CrudAction extends Action {
}

interface GlobalRefreshAction extends CrudAction {
readonly cls: "refresh"
}

interface FetchAction extends CrudAction {
readonly cls: "fetch"
readonly query: string
}

interface UpdateAction extends CrudAction {
readonly cls: "update"
readonly target: string
readonly newValue: Value
}

```

```typescript

function dispatch(c: /* Action | */ GlobalRefreshAction | FetchAction | UpdateAction ): void
{

const fetching = c.cls === "fetch"
const updating = c.cls === "update"

{ /* directly on `c` */

if (fetching) {
c.cls /* "fetch" */
} else {
c.cls /* "refresh" | "update" */
}

}

{ /* on `cStats` */

const cStats = { ...c } as const

/* this already works today */
// if (cStats.cls === "fetch" ) {
// cStats.cls
// }

/* this doesn't currently work */
if (fetching) {
cStats.cls /* still "fetch" | "refresh" | "update" 😒 */
} else {
cStats.cls /* still "fetch" | "refresh" | "update" 😒 */
}

}

...

}
```

Currently, narrowing/constraints doesn't extend across spreads, as shown above, where `cStats.cls` remains `"refresh" | "fetch" | "update"` in either branch.

CFA should ideally, additionally keep track of spreads (and calls) at play. checking on `fetching` should ideally narrow `cStats` to `{ readonly cls: "fetch", readonly query: string }`, given that `c` in `const cStats = { ...c } as const` above narrow to `FetchAction`.

Allow sophisticated boolean types ` is T` to be inferred in, and permitted as, signatures of boolean variables eg `const fetching: /* boolean */ c is FetchAction`. This provides a (necessary) level of type-safety missing so far. This also strengthens basis for more-sophisticated CFA actualisations.

```typescript
/* inferred types */
const fetching /* : c is FetchAction */ = c.cls === "fetch"
const updating /* : c is UpdateAction */ = c.cls === "update"
```

```typescript
/* signatures */
const fetching: /* boolean */ c is FetchAction
const updating: /* boolean */ c is UpdateAction
```

```typescript
/* declaration emit */
interface ActionStats {
readonly c: Action;
readonly fetching: c is FetchAction;
readonly updating: c is UpdateAction;
}
```

```typescript
/* declaration emit */
function getCrudActionStats(c: CrudAction | GlobalRefreshAction | FetchAction | UpdateAction ): {
readonly fetching: c is FetchAction;
readonly updating: c is UpdateAction;
}
```

Allow them to name terms defined outside the function they're written in (#43368, #43786).

```typescript
function isBrowser(): global is Window;
function isWorker(): global is WorkerGlobalScope;
```

```typescript
/* declaration emit */
interface PlatformStats {
readonly browser: global is Window;
readonly workerScope: global is WorkerGlobalScope;
}
```

Allow operations between them - for example, `global is Window || global is NodeGlobalScope` (`stdin` not available (with)in Worker(s)), `(lhs: B, rhs: C) => lhs is G && rhs is H`.

```typescript
/* declaration emit */
/** the service will only be available in select host engines. it's unavailable in, for example, AudioWorklet, PaintWorklet, ServiceWorker, Deno, wherein `available` will return `false`. */
interface PlatformStats {
readonly available: global is Window || global is WorkerGlobalScope || global is NodeGlobalScope;
}
```

```typescript
/* declaration emit */
function m1(lhs: B, rhs: C): lhs is G && rhs is H;
```

more sophisticated examples of binary ops between those types

```typescript
/* declaration emit */
function m2(lhs: B, rhs: D, options: F): (lhs is G && rhs is H ) || lhs is K || (lhs is L && rhs is M) ;
```

```typescript
/* declaration emit */function m3(lhs: B, rhs: D, options: F): (lhs is G || rhs is H ) && (lhs is L || rhs is M) ;
```

Allow their usage in conditional types; allow conditional types to operate directly on preceding parameter terms (`otherParam is M ? T1 : T2`).

```typescript
// function perform(stream: S, c: S extends Writer ? (WriteAction | ReadAction) : ReadAction ): ActionResult

function perform(stream: Stream, c: stream is Writer ? (WriteAction | ReadAction) : ReadAction ): ActionResult

function perform(writes: Boolean, c: writes is true ? (UpdateAction | PruningAction) : (FetchAction | RefreshAction) ): ActionResult
```

- additionally, in case of `otherParam` conforming to `boolean` (or `Boolean`), allow simply writing `otherParam ?` in-place.

```typescript
function perform(writes: boolean, c: writes ? (UpdateAction | PruningAction) : (FetchAction | RefreshAction) ): ActionResult
```

Allow `extends` clause in `interface`s to name union types in place of known-name object types (#202). This avoids the self-dealiasing nature of type-aliases (eg `type T = ...`, `type T = T1 | T2 | ...`), instead acting as nominal types, preserved across assignments, calls and inference (`interface`s are generally inferred as-is, not dealiased).

```typescript
interface CommonAction extends (GlobalRefreshAction | FetchAction | UpdateAction) { }
```

### 💻 Use Cases

### Use-cases

A series of `const ... = ...`s and conditionals.

Working with JSX-based frameworks.
Writing UI components.

Complex logic involving UT(s) (Discriminated Union Type(s)) and conditionals. Some sophisticated inner logic.

Other use-cases with Declaration Emit (DTS Emit; `.d.ts`) at play.

### `interface Action` and `function dispatch` example

```typescript

interface Action {
readonly cls: string & { __c?: any }
}

interface GlobalRefreshAction extends Action {
readonly cls: "refresh"
}

interface FetchAction extends Action {
readonly cls: "fetch"
readonly query: string
}

interface UpdateAction extends Action {
readonly cls: "update"
readonly target: string
readonly newValue: Value
}

// interface CommonAction extends (GlobalRefreshAction | FetchAction | UpdateAction) { }

// function supportsCommonAction(c: CommonAction ): boolean ;

function dispatch(c: /* Action | */ GlobalRefreshAction | FetchAction | UpdateAction )
: void
{

const fetching = c.cls === "fetch"
const updating = c.cls === "update"

{

if (fetching) {
c.cls
} else {
c.cls
}

if (fetching || updating) {
c.cls
} else {
c.cls
}

}

{

const cStats = { ...c } as const

if (cStats.cls === "fetch" ) { cStats.cls }
// if (cStats.cls === "fetch" ) {
// cStats.cls
// } else {
// cStats.cls
// }

if (fetching) {
cStats.cls
} else {
cStats.cls
}

if (fetching || updating) {
cStats.cls
} else {
cStats.cls
}

}

{

const cStats = !fetching ? ({ ...c } as const) : ({ ...c } as const)

// if (cStats.cls === "fetch" ) {
// cStats.cls
// } else {
// cStats.cls
// }

if (fetching) {
cStats.cls
} else {
cStats.cls
}

if (fetching || updating) {
cStats.cls
} else {
cStats.cls
}

}

}

```

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Hướng nghiên cứu

Không có tệp, kiểm thử hoặc điểm vào của trình biên dịch nào được nêu tên. Hãy bắt đầu bằng cách tách các ví dụ tạo động lực thành các yêu cầu có phạm vi độc lập về CFA, vị từ kiểu boolean, kiểu điều kiện và mở rộng interface; để được xem là hoàn thành sẽ cần có phạm vi đã thống nhất cùng với độ bao phủ triển khai và hồi quy cho các trường hợp được chọn.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
typescript
Lĩnh vực
compilers
Loại issue
Tính năng
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Cần làm rõ
Mức phù hợp với người mới
25/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.