🔍 Search Terms
more advanced sophisticated CFA control flow analysis inferred working across assignments variables consts 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
⭐ Suggestion
A demand for more-sophisticated CFA, working across indirections, eg variables, assignments, and spreads (see below).
Sophisticated boolean types <lhs> 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
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<out Value = any> extends CrudAction {
readonly cls: "update"
readonly target: string
readonly newValue: Value
}
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 <lhs> 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.
/* inferred types */
const fetching /* : c is FetchAction */ = c.cls === "fetch"
const updating /* : c is UpdateAction */ = c.cls === "update"
/* signatures */
const fetching: /* boolean */ c is FetchAction
const updating: /* boolean */ c is UpdateAction
/* declaration emit */
interface ActionStats {
readonly c: Action;
readonly fetching: c is FetchAction;
readonly updating: c is UpdateAction;
}
/* 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).
function isBrowser(): global is Window;
function isWorker(): global is WorkerGlobalScope;
/* 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.
/* 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;
}
/* declaration emit */
function m1(lhs: B, rhs: C): lhs is G && rhs is H;
more sophisticated examples of binary ops between those types
/* 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) ;
/* 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).
// function perform<const S>(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
Allow extends clause in interfaces 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 (interfaces are generally inferred as-is, not dealiased).
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
Details
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<out Value = any> 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
}
}
}
🔍 Search Terms
more advanced sophisticated CFA control flow analysis inferred working across assignments variables
consts spreads narrowing constraining typesambient 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
⭐ Suggestion
A demand for more-sophisticated CFA, working across indirections, eg variables, assignments, and spreads (see below).
Sophisticated boolean types
<lhs> is T(Type-Predicate(ing) Boolean Type(s) ) to be inferred in, and permitted as, signatures of boolean variables egconst fetching: /* boolean */ c is FetchAction.📃 Motivating Example
Currently, narrowing/constraints doesn't extend across spreads, as shown above, where
cStats.clsremains"refresh" | "fetch" | "update"in either branch.CFA should ideally, additionally keep track of spreads (and calls) at play. checking on
fetchingshould ideally narrowcStatsto{ readonly cls: "fetch", readonly query: string }, given thatcinconst cStats = { ...c } as constabove narrow toFetchAction.Allow sophisticated boolean types
<lhs> is Tto be inferred in, and permitted as, signatures of boolean variables egconst 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.Allow them to name terms defined outside the function they're written in (#43368, #43786).
Allow operations between them - for example,
global is Window || global is NodeGlobalScope(stdinnot available (with)in Worker(s)),(lhs: B, rhs: C) => lhs is G && rhs is H.more sophisticated examples of binary ops between those types
Allow their usage in conditional types; allow conditional types to operate directly on preceding parameter terms (
otherParam is M ? T1 : T2).additionally, in case of
otherParamconforming toboolean(orBoolean), allow simply writingotherParam ?in-place.Allow
extendsclause ininterfaces to name union types in place of known-name object types (#202). This avoids the self-dealiasing nature of type-aliases (egtype T = ...,type T = T1 | T2 | ...), instead acting as nominal types, preserved across assignments, calls and inference (interfaces are generally inferred as-is, not dealiased).💻 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 Actionandfunction dispatchexampleDetails