Now that TypeScript does control flow based type analysis, and there is a never type in the works, is it possible to consider providing better type checking around assert(...) function calls that assert that a variable has a certain type at runtime?
TL;DR: some assert functions are really just type guards that signal via return/throw rather than true/false. Example:
assert(typeof s === "string"); // throws if s is not a string
s.length; // s is narrowed to string here...
Problem
Asserts are common in contract-based programming, but I've also been coming across this scenario regularly whilst traversing JavaScript ASTs based on the Parser API (I'm using babel to produce/traverse ASTs).
For example, consider the MemberExpression:

Note we can assume property is an Identifier if computed===false. This is what I'd like to write:
function foo(expr: MemberExprssion) {
if (expr.computed) {
// handle computed case
}
else {
// since computed===false, we know property must be an Identifier
assert(isIdentifier(expr.property));
let name = expr.property.name; // ERROR: name doesn't exist on Identifier|Expression
}
}
Unfortunately that doesn't compile, because expr.property does not get narrowed after the assert(...) call.
To get the full benefit of control flow analysis currently, you have to expand the assert call inline:
...
else {
if (!isIdentifier(expr.property)) {
throw new AssertionError(`Expected property to be an Identifier`);
}
let name = expr.property.name; // OK
}
...
While preparing the typings for babel-core, babel-types and friends, I noticed that using asserts this way is the norm. babel-types actually provides an assertXXX method for every isXXX method. These assertXXX functions are really just type guards that signal via return/throw rather than true/false.
Possible Solutions?
Not sure if it's feasible at all! But the new work on never in #8652 suggests a few possibilities.
Specific assertions: assertIsT(...)
// normal type guard
function isIdentifier(n: Node): n is Identifier {
return n.type === 'Identifier';
}
// PROPOSED SYNTAX: assert type guard
function assertIdentifier(n: Node): n is Identifier | never {
if (n.type !== 'Identifier') {
throw new AssertionError(`Expected an Identifier`);
}
}
The compiler would reason that if this assert call returns at all, then it can safely narrow the variable type in following code.
General assertions used with type guards: assert(isT(...))
The more general assert(cond: boolean) function would need a different approach and might not be feasible, but here's an idea:
// General case
declare function assert(cond: boolean): void;
// PROPOSED SYNTAX: Special overload for expressions of the form assert(isT(x))
declare function assert<T>(guard: guard is T): void | never;
For that second assert overload to work, the compiler on seeing assert(isT(x)) would have to somehow forward the x is T narrowing from the isT(x) expression to the assert(...) expression at compile-time.
Would be great if it also detected/handled things like assert(typeof x == 'string').
Not sure if any of this would meet the cost/benefit bar, but it's just an idea.
Now that TypeScript does control flow based type analysis, and there is a
nevertype in the works, is it possible to consider providing better type checking aroundassert(...)function calls that assert that a variable has a certain type at runtime?TL;DR: some
assertfunctions are really just type guards that signal viareturn/throwrather thantrue/false. Example:Problem
Asserts are common in contract-based programming, but I've also been coming across this scenario regularly whilst traversing JavaScript ASTs based on the Parser API (I'm using babel to produce/traverse ASTs).
For example, consider the
MemberExpression:Note we can assume
propertyis anIdentifierifcomputed===false. This is what I'd like to write:Unfortunately that doesn't compile, because
expr.propertydoes not get narrowed after theassert(...)call.To get the full benefit of control flow analysis currently, you have to expand the assert call inline:
While preparing the typings for
babel-core,babel-typesand friends, I noticed that using asserts this way is the norm.babel-typesactually provides anassertXXXmethod for everyisXXXmethod. TheseassertXXXfunctions are really just type guards that signal viareturn/throwrather thantrue/false.Possible Solutions?
Not sure if it's feasible at all! But the new work on
neverin #8652 suggests a few possibilities.Specific assertions: assertIsT(...)
The compiler would reason that if this assert call returns at all, then it can safely narrow the variable type in following code.
General assertions used with type guards: assert(isT(...))
The more general
assert(cond: boolean)function would need a different approach and might not be feasible, but here's an idea:For that second
assertoverload to work, the compiler on seeingassert(isT(x))would have to somehow forward thex is Tnarrowing from theisT(x)expression to theassert(...)expression at compile-time.Would be great if it also detected/handled things like
assert(typeof x == 'string').Not sure if any of this would meet the cost/benefit bar, but it's just an idea.