microsoft / microsoft/TypeScript

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

Aperta
#64,047 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Suggestion
Lingua principale
Go
Stelle
111k
Fork
14.3k
Merge medio
2g 4h
PR unite (30g)
132

Descrizione

### 🔍 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
}

}

}

```

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Non vengono indicati file, test o punti di ingresso del compilatore. Inizia separando gli esempi motivanti in requisiti con ambiti indipendenti per CFA, predicati di tipo booleani, tipi condizionali ed estensioni delle interfacce; il lavoro sarebbe completo solo con un ambito concordato e una copertura di implementazione e di regressione per i casi selezionati.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
typescript
Ambito
compilers
Tipo di issue
Funzionalità
Difficoltà
5/5
Tempo stimato
Più di una settimana
Stato di attività
Attiva
Chiarezza
Da chiarire
Idoneità per principianti
25/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.