Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assertions in control flow analysis #32695

Open
wants to merge 16 commits into
base: master
from

Conversation

@ahejlsberg
Copy link
Member

commented Aug 3, 2019

With this PR we reflect the effects of calls to assert(...) functions and never-returning functions in control flow analysis.

The PR introduces a new asserts modifier that can be used in type predicates:

declare function assert(value: unknown): asserts value;
declare function assertIsArrayOfStrings(obj: unknown): asserts obj is string[];
declare function assertNonNull<T>(obj: T): asserts obj is NonNullable<T>;

An asserts return type predicate indicates that the function returns only when the assertion holds and otherwise throws an exception. Specifically, the assert x form indicates that the function returns only when x is truthy, and the assert x is T form indicates that the function returns only when x is of type T. An asserts return type predicate implies that the returned value is of type void, and there is no provision for returning values of other types.

The effects of calls to functions with asserts type predicates are reflected in control flow analysis. For example:

function f1(x: unknown) {
    assert(typeof x === "string");
    return x.length;  // x has type string here
}

function f2(x: unknown) {
    assertIsArrayOfStrings(x);
    return x[0].length;  // x has type string[] here
}

function f3(x: string | undefined) {
    assertNonNull(x);
    return x.length;  // x has type string here
}

From a control flow analysis perspective, a call to a function with an asserts x return type is equivalent to an if statement that throws when x is falsy. For example, the control flow of f1 above is analyzed equivalently to

function f1(x: unknown) {
    if (!(typeof x === "string")) {
        throw ...;
    }
    return x.length;  // x has type string here
}

Similarly, a call to a function with an asserts x is T return type is equivalent to an if statement that throws when a call to a function with an x is T return type returns false. In other words, given

declare function isArrayOfStrings(obj: unknown): obj is string[];

the control flow of f2 above is analyzed equivalently to

function f2(x: unknown) {
    if (!isArrayOfStrings(x)) {
        throw ...;
    }
    return x[0].length;  // x has type string[] here
}

Effectively, assertIsArrayOfStrings(x) is just shorthand for assert(isArrayOfStrings(x)).

In addition to support for asserts, we now reflect effects of calls to never-returning functions in control flow analysis.

function fail(message?: string): never {
    throw new Error(message);
}

function f3(x: string | undefined) {
    if (x === undefined) fail("undefined argument");
    x.length;  // Type narrowed to string
}

function f4(x: number): number {
    if (x >= 0) return x;
    fail("negative number");
    x;  // Type narrowed to never
}

Note that f4 is considered to not have an implicit return that contributes undefined to the return value. Without the call to fail an error would have been reported.

A function call is analyzed as an assertion call or never-returning call when

  • the call occurs as a top-level expression statement, and
  • the call specifies a single identifier or a dotted sequence of identifiers for the function name, and
  • each identifier in the function name references an entity with an explicit type, and
  • the function name resolves to a function type with an asserts return type or an explicit never return type annotation.

An entity is considered to have an explicit type when it is declared as a function, method, class or namespace, or as a variable, parameter or property with an explicit type annotation. (This particular rule exists so that control flow analysis of potential assertion calls doesn't circularly trigger further analysis.)

EDIT: Updated to include effects of calls to never-returning functions.

Fixes #8655.

@acutmore

This comment has been minimized.

Copy link

commented Aug 3, 2019

Really like this!

Curious if instead of asserts x it was considered to special case asserts x is true? Might be easier for people to learn/read for the cost of more complexity in the compiler

@ahejlsberg

This comment has been minimized.

Copy link
Member Author

commented Aug 3, 2019

Curious if instead of asserts x it was considered to special case asserts x is true?

No, because the two are not equivalent. asserts x reflects the full effects of a logical expression when x is truthy, similar to an equivalent if statement. assert x is true simply narrows the type of a variable passed for x, similar to the effects of passing x to an equivalent user defined type predicate function.

@ahejlsberg

This comment has been minimized.

Copy link
Member Author

commented Aug 3, 2019

@typescript-bot perf test this

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

commented Aug 3, 2019

Heya @ahejlsberg, I've started to run the perf test suite on this PR at fe70a62. You can monitor the build here. It should now contribute to this PR's status checks.

Update: The results are in!

@dragomirtitian

This comment has been minimized.

Copy link
Contributor

commented Aug 3, 2019

@ahejlsberg Just curios, the official position when multiple such issues were raised was that adding all potential call expressions will grow the CF graph to much and thus it was not really feasible to add this feature. My question is what changed ? Was the reasoning flawed, other performance improvements now make this less of a perf concern, or this is still experimental and could still be axed if performance does meet expectations ?

@acutmore

This comment has been minimized.

Copy link

commented Aug 3, 2019

Ah! So asserts x declares it’s checking ‘truthy' rather than ‘true’

declare function assert(x): asserts x;
declare const x: string | null;
assert(x);
x.length; // x narrowed to string
@ahejlsberg

This comment has been minimized.

Copy link
Member Author

commented Aug 3, 2019

Ah! So asserts x declares it’s checking ‘truthy' rather than ‘true’

It's not just that.

asserts x reflects the full effects of the logical expression passed as an argument. E.g. assert(typeof x === "string" || typeof x === "number") narrows x to string | number in the following statements.

assert x is true however only affects a variable passed as an argument, i.e. assertIsTrue(x) narrows x to type true in the following, but does not reflect the effects of a logical expression passed as an argument.

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

commented Aug 3, 2019

@ahejlsberg
The results of the perf run you requested are in!

Here they are:

Comparison Report - master..32695

Metric master 32695 Delta Best Worst
Angular - node (v12.1.0, x64)
Memory used 325,416k (± 0.03%) 325,819k (± 0.02%) +403k (+ 0.12%) 325,735k 326,007k
Parse Time 1.44s (± 0.91%) 1.43s (± 0.74%) -0.01s (- 0.97%) 1.41s 1.45s
Bind Time 0.76s (± 0.78%) 0.77s (± 1.49%) +0.00s (+ 0.52%) 0.75s 0.81s
Check Time 4.22s (± 0.44%) 4.27s (± 0.35%) +0.04s (+ 1.07%) 4.24s 4.32s
Emit Time 5.21s (± 0.49%) 5.27s (± 0.82%) +0.06s (+ 1.07%) 5.21s 5.39s
Total Time 11.64s (± 0.33%) 11.73s (± 0.48%) +0.09s (+ 0.78%) 11.65s 11.90s
Monaco - node (v12.1.0, x64)
Memory used 345,830k (± 0.02%) 346,156k (± 0.02%) +326k (+ 0.09%) 346,045k 346,293k
Parse Time 1.19s (± 0.64%) 1.17s (± 0.48%) -0.02s (- 1.52%) 1.16s 1.18s
Bind Time 0.67s (± 0.33%) 0.68s (± 0.54%) +0.00s (+ 0.75%) 0.67s 0.68s
Check Time 4.28s (± 0.44%) 4.29s (± 0.35%) +0.01s (+ 0.28%) 4.26s 4.32s
Emit Time 2.84s (± 0.67%) 2.85s (± 0.97%) +0.00s (+ 0.18%) 2.81s 2.93s
Total Time 8.97s (± 0.38%) 8.98s (± 0.28%) +0.01s (+ 0.09%) 8.94s 9.05s
TFS - node (v12.1.0, x64)
Memory used 301,335k (± 0.02%) 301,644k (± 0.02%) +310k (+ 0.10%) 301,532k 301,724k
Parse Time 0.92s (± 0.84%) 0.91s (± 0.92%) -0.02s (- 1.74%) 0.89s 0.92s
Bind Time 0.62s (± 1.08%) 0.63s (± 0.80%) +0.01s (+ 1.30%) 0.61s 0.63s
Check Time 3.83s (± 0.46%) 3.86s (± 0.54%) +0.03s (+ 0.89%) 3.79s 3.89s
Emit Time 2.94s (± 0.63%) 2.96s (± 0.76%) +0.02s (+ 0.75%) 2.89s 3.01s
Total Time 8.30s (± 0.34%) 8.34s (± 0.26%) +0.05s (+ 0.57%) 8.30s 8.38s
Angular - node (v8.9.0, x64)
Memory used 343,931k (± 0.01%) 344,376k (± 0.01%) +445k (+ 0.13%) 344,294k 344,499k
Parse Time 1.93s (± 0.34%) 1.83s (± 0.36%) -0.10s (- 5.12%) 1.82s 1.85s
Bind Time 0.82s (± 0.57%) 0.82s (± 0.91%) -0.00s (- 0.37%) 0.81s 0.84s
Check Time 5.07s (± 0.33%) 5.10s (± 0.81%) +0.03s (+ 0.61%) 5.03s 5.19s
Emit Time 6.08s (± 0.57%) 5.97s (± 1.60%) -0.11s (- 1.88%) 5.71s 6.12s
Total Time 13.90s (± 0.37%) 13.72s (± 0.70%) -0.18s (- 1.32%) 13.48s 13.86s
Monaco - node (v8.9.0, x64)
Memory used 363,317k (± 0.01%) 363,607k (± 0.01%) +290k (+ 0.08%) 363,514k 363,675k
Parse Time 1.52s (± 0.45%) 1.43s (± 0.31%) -0.09s (- 6.10%) 1.42s 1.44s
Bind Time 0.88s (± 0.39%) 0.88s (± 1.68%) +0.01s (+ 0.68%) 0.86s 0.92s
Check Time 5.28s (± 0.35%) 5.20s (± 1.37%) -0.08s (- 1.46%) 5.03s 5.30s
Emit Time 2.93s (± 0.37%) 3.14s (± 6.26%) +0.21s (+ 7.23%) 2.91s 3.49s
Total Time 10.61s (± 0.14%) 10.67s (± 1.38%) +0.05s (+ 0.50%) 10.49s 10.96s
TFS - node (v8.9.0, x64)
Memory used 317,282k (± 0.02%) 317,510k (± 0.01%) +228k (+ 0.07%) 317,443k 317,563k
Parse Time 1.23s (± 0.61%) 1.13s (± 0.33%) -0.10s (- 7.95%) 1.13s 1.14s
Bind Time 0.66s (± 0.74%) 0.67s (± 0.67%) +0.00s (+ 0.60%) 0.66s 0.68s
Check Time 4.47s (± 0.57%) 4.49s (± 0.42%) +0.02s (+ 0.54%) 4.45s 4.53s
Emit Time 3.05s (± 0.66%) 3.19s (± 1.73%) +0.13s (+ 4.35%) 3.05s 3.26s
Total Time 9.42s (± 0.33%) 9.48s (± 0.55%) +0.06s (+ 0.66%) 9.36s 9.58s
Angular - node (v8.9.0, x86)
Memory used 194,854k (± 0.02%) 195,000k (± 0.02%) +146k (+ 0.08%) 194,928k 195,056k
Parse Time 1.87s (± 0.46%) 1.78s (± 0.47%) -0.09s (- 4.76%) 1.76s 1.80s
Bind Time 0.95s (± 0.87%) 0.96s (± 0.73%) +0.01s (+ 0.52%) 0.94s 0.97s
Check Time 4.59s (± 0.72%) 4.65s (± 0.56%) +0.07s (+ 1.46%) 4.61s 4.72s
Emit Time 5.85s (± 0.87%) 5.78s (± 0.83%) -0.07s (- 1.20%) 5.67s 5.87s
Total Time 13.26s (± 0.54%) 13.17s (± 0.48%) -0.09s (- 0.65%) 13.03s 13.33s
Monaco - node (v8.9.0, x86)
Memory used 202,921k (± 0.01%) 203,068k (± 0.03%) +147k (+ 0.07%) 202,941k 203,192k
Parse Time 1.59s (± 0.56%) 1.48s (± 0.56%) -0.10s (- 6.37%) 1.47s 1.50s
Bind Time 0.70s (± 0.52%) 0.71s (± 0.56%) +0.01s (+ 0.99%) 0.70s 0.72s
Check Time 4.90s (± 0.44%) 4.90s (± 0.74%) +0.00s (+ 0.02%) 4.79s 4.96s
Emit Time 3.19s (± 0.76%) 3.18s (± 0.97%) -0.01s (- 0.38%) 3.13s 3.25s
Total Time 10.38s (± 0.30%) 10.28s (± 0.45%) -0.11s (- 1.02%) 10.14s 10.37s
TFS - node (v8.9.0, x86)
Memory used 178,237k (± 0.02%) 178,315k (± 0.02%) +78k (+ 0.04%) 178,185k 178,389k
Parse Time 1.30s (± 0.65%) 1.19s (± 0.93%) -0.11s (- 8.22%) 1.17s 1.23s
Bind Time 0.62s (± 1.42%) 0.64s (± 1.20%) +0.01s (+ 1.93%) 0.63s 0.66s
Check Time 4.28s (± 0.70%) 4.34s (± 0.92%) +0.06s (+ 1.28%) 4.27s 4.41s
Emit Time 2.88s (± 0.45%) 2.84s (± 2.22%) -0.04s (- 1.35%) 2.64s 2.93s
Total Time 9.09s (± 0.41%) 9.01s (± 0.81%) -0.08s (- 0.90%) 8.84s 9.16s
Angular - node (v9.0.0, x64)
Memory used 343,623k (± 0.01%) 343,948k (± 0.02%) +325k (+ 0.09%) 343,706k 344,059k
Parse Time 1.68s (± 0.49%) 1.67s (± 0.44%) -0.01s (- 0.42%) 1.66s 1.69s
Bind Time 0.77s (± 0.84%) 0.77s (± 0.68%) +0.00s (+ 0.13%) 0.76s 0.78s
Check Time 4.78s (± 0.51%) 4.83s (± 0.57%) +0.05s (+ 0.98%) 4.78s 4.91s
Emit Time 5.69s (± 1.81%) 5.48s (± 0.98%) -0.22s (- 3.85%) 5.37s 5.61s
Total Time 12.92s (± 0.87%) 12.74s (± 0.30%) -0.18s (- 1.37%) 12.68s 12.84s
Monaco - node (v9.0.0, x64)
Memory used 363,060k (± 0.03%) 363,350k (± 0.03%) +290k (+ 0.08%) 363,188k 363,663k
Parse Time 1.29s (± 0.90%) 1.27s (± 0.52%) -0.02s (- 1.24%) 1.26s 1.28s
Bind Time 0.85s (± 0.58%) 0.86s (± 0.88%) +0.00s (+ 0.23%) 0.84s 0.88s
Check Time 4.91s (± 0.45%) 4.91s (± 0.50%) -0.00s (- 0.02%) 4.85s 4.94s
Emit Time 3.36s (± 0.43%) 3.37s (± 0.34%) +0.00s (+ 0.12%) 3.35s 3.40s
Total Time 10.41s (± 0.36%) 10.40s (± 0.37%) -0.01s (- 0.09%) 10.32s 10.50s
TFS - node (v9.0.0, x64)
Memory used 317,006k (± 0.02%) 317,300k (± 0.02%) +293k (+ 0.09%) 317,182k 317,415k
Parse Time 1.02s (± 0.51%) 1.01s (± 0.47%) -0.01s (- 1.08%) 1.00s 1.02s
Bind Time 0.61s (± 0.97%) 0.61s (± 0.97%) +0.00s (+ 0.16%) 0.60s 0.63s
Check Time 4.34s (± 0.47%) 4.39s (± 0.33%) +0.05s (+ 1.13%) 4.35s 4.42s
Emit Time 3.20s (± 0.62%) 3.18s (± 0.48%) -0.01s (- 0.44%) 3.15s 3.21s
Total Time 9.18s (± 0.33%) 9.20s (± 0.27%) +0.02s (+ 0.20%) 9.14s 9.25s
Angular - node (v9.0.0, x86)
Memory used 194,883k (± 0.02%) 195,048k (± 0.04%) +165k (+ 0.08%) 194,887k 195,228k
Parse Time 1.60s (± 0.55%) 1.59s (± 0.37%) -0.01s (- 0.88%) 1.58s 1.60s
Bind Time 0.88s (± 0.53%) 0.89s (± 1.07%) +0.01s (+ 0.68%) 0.87s 0.91s
Check Time 4.30s (± 0.69%) 4.34s (± 0.75%) +0.04s (+ 0.91%) 4.29s 4.45s
Emit Time 5.53s (± 0.99%) 5.54s (± 0.71%) +0.01s (+ 0.22%) 5.47s 5.65s
Total Time 12.31s (± 0.59%) 12.35s (± 0.46%) +0.04s (+ 0.37%) 12.26s 12.47s
Monaco - node (v9.0.0, x86)
Memory used 202,929k (± 0.02%) 203,055k (± 0.03%) +126k (+ 0.06%) 202,917k 203,166k
Parse Time 1.32s (± 0.85%) 1.31s (± 0.67%) -0.01s (- 0.45%) 1.30s 1.34s
Bind Time 0.64s (± 0.62%) 0.65s (± 0.58%) +0.00s (+ 0.62%) 0.64s 0.65s
Check Time 4.70s (± 0.56%) 4.73s (± 0.71%) +0.03s (+ 0.68%) 4.67s 4.83s
Emit Time 3.09s (± 0.28%) 3.09s (± 0.56%) -0.00s (- 0.03%) 3.04s 3.12s
Total Time 9.75s (± 0.32%) 9.78s (± 0.42%) +0.03s (+ 0.28%) 9.70s 9.88s
TFS - node (v9.0.0, x86)
Memory used 178,233k (± 0.01%) 178,312k (± 0.03%) +79k (+ 0.04%) 178,185k 178,407k
Parse Time 1.04s (± 0.72%) 1.03s (± 0.48%) -0.01s (- 1.06%) 1.01s 1.03s
Bind Time 0.57s (± 1.19%) 0.57s (± 1.19%) +0.00s (+ 0.17%) 0.56s 0.59s
Check Time 4.12s (± 0.51%) 4.14s (± 0.60%) +0.02s (+ 0.39%) 4.08s 4.19s
Emit Time 2.79s (± 1.18%) 2.78s (± 1.13%) -0.01s (- 0.43%) 2.73s 2.88s
Total Time 8.53s (± 0.53%) 8.52s (± 0.50%) -0.00s (- 0.06%) 8.44s 8.63s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-142-generic
Architecturex64
Available Memory16 GB
Available Memory1 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v12.1.0, x64)
  • node (v8.9.0, x64)
  • node (v8.9.0, x86)
  • node (v9.0.0, x64)
  • node (v9.0.0, x86)
Scenarios
  • Angular - node (v12.1.0, x64)
  • Angular - node (v8.9.0, x64)
  • Angular - node (v8.9.0, x86)
  • Angular - node (v9.0.0, x64)
  • Angular - node (v9.0.0, x86)
  • Monaco - node (v12.1.0, x64)
  • Monaco - node (v8.9.0, x64)
  • Monaco - node (v8.9.0, x86)
  • Monaco - node (v9.0.0, x64)
  • Monaco - node (v9.0.0, x86)
  • TFS - node (v12.1.0, x64)
  • TFS - node (v8.9.0, x64)
  • TFS - node (v8.9.0, x86)
  • TFS - node (v9.0.0, x64)
  • TFS - node (v9.0.0, x86)
Benchmark Name Iterations
Current 32695 10
Baseline master 10

@ahejlsberg

This comment has been minimized.

Copy link
Member Author

commented Aug 3, 2019

@dragomirtitian What changed? First realizing that the CFA node to AST node ratio is pretty low (about 10% for the compiler itself, for example), and further that we can restrict ourselves to only including top-level expression statement call nodes in the CFA graph. Again, using the compiler itself as an example, this PR only increases the number of CFA nodes by 7.5%. So, overall we're talking less than 1% of additional memory overhead. And execution time overhead is very low when CFA call nodes turn out to not be assertions.

The perf test bot numbers appear to confirm this. Less that 0.1% memory overhead and zero execution time overhead. If anything, I would actually have expected more impact.

I guess it's sometimes good to question conventional wisdom. Even when it's your own!

@acutmore

This comment has been minimized.

Copy link

commented Aug 3, 2019

asserts x reflects the full effects of the logical expression passed as an argument.

This I understand :-). What I am doing a poor job of expressing was that in my mind the reflection is a detail of the call site, and theoretically a function that asserts x is true could be completely obviously to this. Though the more I think about this, the more I can see how that would involve a lot of complexity. As it would almost be similar to supporting something like this:

declare const x: string | number;
const isString = typeof x === 'string'; // isString: (false & x is number) | (true & x is string);

if (isString) {
    x; // x: string;
}

So I retract all I have said, and have fully joined the asserts x fanclub!

@felixfbecker

This comment has been minimized.

Copy link

commented Aug 3, 2019

Love this as it would make input validation (even against something like a JSON schema) a lot less clunky!

What makes me think though: Have you thought about expressing this with return types instead?

assertString<T>(value: T): T extends string ? void : never

Assertion functions are really just functions that throw errors in certain cases. A function returning never means it is always throwing. If a function returns never exactly when the input is a string (i.e. always throws when the input is a string), we know that after that call the value must be a string.

This was also suggested and upvoted in the issue: #8655 (comment)
I think if this feature can be expressed with existing syntax and concepts, adding more keywords and concepts to the language should be avoided (or TS will eventually become too complex).

The only thing a conditional never types cannot express is a manual type checking boolean expression:

assert(typeof x === 'string')

but I think that is actually a good thing. People should use specialized assertion functions, because they would throw an assertion error like

Expected type of value to be string, got number

instead of

Expected false to be true

which is not helpful. Plain assert() should always be avoided.

It also seems like asserts would not work with the popular expect() assertion style (used in Jest):

expect(someValue).toBeString()


function expect<T>(value: T): Matcher<T>
interface Matcher<T> {
  toBeString(): asserts ??? is string; // can't reference value here anymore
}

while that would work great with never return types:

function expect<T>(value: T): Matcher<T>
interface Matcher<T> {
  toBeString(): T extends string ? void : never;
}
@ajafff
Copy link
Contributor

left a comment

technically it's a breaking change, because the following code no longer parses without error (but what are the odds such code really exists?)

declare function f(asserts: unknown): asserts is string;
@@ -1276,6 +1284,22 @@ namespace ts {
activeLabels!.pop();
}

function isDottedName(node: Expression): boolean {

This comment has been minimized.

Copy link
@ajafff

ajafff Aug 3, 2019

Contributor

what's the difference to isEntityNameExpression?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Aug 3, 2019

Author Member

Good catch! No difference, will change to use the existing function.

@@ -1276,6 +1284,22 @@ namespace ts {
activeLabels!.pop();
}

function isDottedName(node: Expression): boolean {
return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression && isDottedName((<PropertyAccessExpression>node).expression);

This comment has been minimized.

Copy link
@ajafff

ajafff Aug 3, 2019

Contributor

Is there a reason not to include this and super in property access expressions?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Aug 3, 2019

Author Member

I think that would be okay, but I'll have to convince myself it can't trigger circularities in control flow analysis.

node.assertsModifier = parseExpectedToken(SyntaxKind.AssertsKeyword);
node.parameterName = parseIdentifier();
if (parseOptional(SyntaxKind.IsKeyword)) {
node.type = parseType();

This comment has been minimized.

Copy link
@ajafff

ajafff Aug 3, 2019

Contributor

This makes this type of object polymorphic. Could you instead always assign the property and use undefined if there is no type?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Aug 3, 2019

Author Member

Yup

@@ -3225,6 +3228,16 @@ namespace ts {
}
}

function parseAssertsTypePredicate(): TypeNode {
const node = <TypePredicateNode>createNode(SyntaxKind.TypePredicate);
node.assertsModifier = parseExpectedToken(SyntaxKind.AssertsKeyword);

This comment has been minimized.

Copy link
@ajafff

ajafff Aug 3, 2019

Contributor

adding this property here and not assigning it in parseTypeOrTypePredicate where regular TypePredicate nodes are constructed, create yet another hidden class that hinders optimization at runtime.
Either assign it last in this function or (even better) assign it in both functions in the same order

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Aug 3, 2019

Author Member

Agreed

@@ -3225,6 +3228,16 @@ namespace ts {
}
}

function parseAssertsTypePredicate(): TypeNode {
const node = <TypePredicateNode>createNode(SyntaxKind.TypePredicate);
node.assertsModifier = parseExpectedToken(SyntaxKind.AssertsKeyword);

This comment has been minimized.

Copy link
@ajafff

ajafff Aug 3, 2019

Contributor

Is there a possibility that there will be more modifiers in the future? If so, would it make sense to put this into Node#modifiers?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Aug 3, 2019

Author Member

It's possible, but for now I'm going to keep it the way it is.

@@ -667,17 +667,18 @@ namespace ts {
return <KeywordTypeNode>createSynthesizedNode(kind);
}

export function createTypePredicateNode(parameterName: Identifier | ThisTypeNode | string, type: TypeNode) {
export function createTypePredicateNode(assertsModifier: AssertsToken | undefined, parameterName: Identifier | ThisTypeNode | string, type: TypeNode | undefined) {

This comment has been minimized.

Copy link
@ajafff

ajafff Aug 3, 2019

Contributor

this is a breaking API change.
typically there is a new overload added to maintain backwards compatibility. the old signature can be marked as deprecated right away and could be removed later.

@@ -16845,23 +16820,62 @@ namespace ts {
return isLengthPushOrUnshift || isElementAssignment;
}

function maybeTypePredicateCall(node: CallExpression) {
function isDeclarationWithExplicitTypeAnnotation(declaration: Declaration | undefined) {

This comment has been minimized.

Copy link
@ajafff

ajafff Aug 3, 2019

Contributor

should this handle JSDoc as well?

@treybrisbane

This comment has been minimized.

Copy link

commented Aug 4, 2019

It looks as though this can be used to track mutations, e.g.:

class Foo {
  constructor(public bar: boolean) {}

  setBar<T extends boolean>(newBar: T): asserts this is Foo & { bar: T } {
    this.bar = newBar;
  }
}

const Foo = new Foo(false);
// foo is Foo
foo.setBar(true);
// foo is Foo & { bar: true }

Or

type Foo = { bar: boolean };

function setBar<T extends boolean>(foo: Foo, newBar: T): asserts foo is Foo & { bar: T } {
  foo.bar = newBar;
}

const foo: Foo = { bar: false };
// foo is Foo
setBar(foo, true);
// foo is Foo & { bar: true }

Is this correct?

@j-f1

This comment has been minimized.

Copy link

commented Aug 4, 2019

Another advantage to using the never type instead as suggested above is that it would also add support for calling e.g. process.exit in a conditional to narrow the type.

@kitsonk kitsonk referenced this pull request Aug 4, 2019
@zenozen

This comment has been minimized.

Copy link

commented Aug 4, 2019

Really nice!

Maybe we could use “asserts false” to represent a function that does not return? (Throws exception) This could help a bunch of case like assertNever, or unimplemented? Or maybe just “assert x is never” works?

@krzkaczor

This comment has been minimized.

Copy link

commented Aug 4, 2019

@ahejlsberg I have a couple of questions:

  1. Does it work with async assert as well? Ie. a function that depending on a condition resolves/rejects a promise.
  2. Does it work with assertions on this? I wonder if this can help implement linear types (related: #16148). My dummy example:
class Socket {
    public async open() asserts this is CloseableSocket {
        console.log("Opening...")
    }

    public async close() asserts this is OpenableSocket {
        console.log("Closing...")
    }
}

interface CloseableSocket{
    close() asserts this is OpenableSocket;
}

interface OpenableSocket{
    open() asserts this is CloseableSocket;
}

Now it would be impossible to call open on the already opened socket and close the already closed socket. This would be really cool to see!

@goodmind

This comment has been minimized.

Copy link

commented Aug 4, 2019

How to write invariant with it?

@jack-williams

This comment has been minimized.

Copy link
Collaborator

commented Aug 4, 2019

@treybrisbane Your second example works, but your first does not because this is not supported in an assert predicate (not sure if that is by-design). So you can track mutations, but this only really works for monotonic references.

@krzkaczor Pre-emptive apology for the pedantry, sorry. What you implement there is known as type-state. Linear (or affine) types are required to soundly implement type-state, but that code does not actually guarantee there is only one reference to a given object. That still looks like an interesting use of this PR though, and if you assume that the user is careful with their aliasing you might be able to add a lot of type-safety.

@felixfbecker

The syntax:

assertString(value: unknown): value extends string ? void : never

also relies on new concepts, specifically having an expression (identifier) appearing in the check-type of a conditional type. On the surface I think it looks familiar to existing ideas, but there may be a non-trivial amount of new concepts required to implement and explain that feature thoroughly.

I think if you want meaningful assertion messages (which is definitely desirable), it could be written like:

function assertString(value: unknown): asserts value is string {
    if (typeof value !== "string") {
        throw "Expected 'string', got ${typeof value}";
    }
}
@felixfbecker

This comment has been minimized.

Copy link

commented Aug 4, 2019

@jack-williams sorry, updated my comment, what I meant was:

assertString<T>(value: T): T extends string ? void : never

which does not require any new concepts. In fact, I would argue, it is almost a bit unexpected that this does not work already, because the semantics of never would lead to this conclusion. TypeScript already infers the never type for functions that always throw, and flags unreachable code after the throw statement. One would think that the fact that the function returns never would also make TS flag code after a call of such function (but doesn't atm). Then by using conditional types we can intuitively model assertions.

@alexreardon

This comment has been minimized.

Copy link

commented Aug 4, 2019

This is needed to correctly move tiny-invariant to typescript: alexreardon/tiny-invariant#45. We have not been able to write a correct typescript invariant

We also use invariant heavily for react-beautiful-dnd, so having this style of guard would making moving rbd over to Typescript much easier atlassian/react-beautiful-dnd#982

@ahejlsberg

This comment has been minimized.

Copy link
Member Author

commented Aug 5, 2019

@felixfbecker The difference between the two forms

assertString(value: unknown): asserts value extends string;
assertString<T>(value: T): T extends string ? void : never

is that that we cannot necessarily make conclusions about an argument passed for value from a type argument for T. For example, imagine a type argument was explicitly specified for T, or that multiple parameters reference T, or that T is only referenced in a composite type and not as a naked type parameter. In those cases it is not meaningful to make conclusions for value and we would need rules to exclude them. Which ultimately leads you to the current form.

@ahejlsberg

This comment has been minimized.

Copy link
Member Author

commented Aug 5, 2019

@typescript-bot perf test this again to observe effects of including this.xxx(...) calls in control flow graph.

@ahejlsberg

This comment has been minimized.

Copy link
Member Author

commented Aug 8, 2019

@typescript-bot perf test this to observe effects of including calls with no arguments in control flow graph.

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

commented Aug 8, 2019

Heya @ahejlsberg, I've started to run the perf test suite on this PR at df02ad6. You can monitor the build here. It should now contribute to this PR's status checks.

Update: The results are in!

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

commented Aug 8, 2019

@ahejlsberg
The results of the perf run you requested are in!

Here they are:

Comparison Report - master..32695

Metric master 32695 Delta Best Worst
Angular - node (v12.1.0, x64)
Memory used 325,308k (± 0.01%) 326,266k (± 0.05%) +958k (+ 0.29%) 325,671k 326,434k
Parse Time 1.44s (± 0.81%) 1.43s (± 0.68%) -0.01s (- 0.49%) 1.41s 1.45s
Bind Time 0.76s (± 0.68%) 0.77s (± 0.99%) +0.01s (+ 0.79%) 0.75s 0.78s
Check Time 4.21s (± 0.57%) 4.29s (± 0.41%) +0.08s (+ 1.83%) 4.24s 4.32s
Emit Time 5.23s (± 0.78%) 5.23s (± 0.71%) -0.00s (- 0.08%) 5.15s 5.32s
Total Time 11.64s (± 0.43%) 11.71s (± 0.40%) +0.07s (+ 0.60%) 11.64s 11.82s
Monaco - node (v12.1.0, x64)
Memory used 345,855k (± 0.02%) 346,322k (± 0.01%) +467k (+ 0.14%) 346,271k 346,446k
Parse Time 1.18s (± 0.51%) 1.17s (± 0.57%) -0.02s (- 1.52%) 1.15s 1.18s
Bind Time 0.67s (± 0.88%) 0.68s (± 0.91%) +0.01s (+ 0.74%) 0.67s 0.70s
Check Time 4.27s (± 0.56%) 4.31s (± 0.46%) +0.04s (+ 0.87%) 4.27s 4.36s
Emit Time 2.84s (± 0.65%) 2.84s (± 0.89%) -0.00s (- 0.11%) 2.77s 2.90s
Total Time 8.98s (± 0.44%) 8.99s (± 0.43%) +0.02s (+ 0.21%) 8.89s 9.07s
TFS - node (v12.1.0, x64)
Memory used 301,408k (± 0.02%) 301,965k (± 0.02%) +557k (+ 0.18%) 301,842k 302,155k
Parse Time 0.92s (± 0.57%) 0.90s (± 0.62%) -0.02s (- 1.85%) 0.89s 0.91s
Bind Time 0.63s (± 0.80%) 0.63s (± 0.54%) +0.00s (+ 0.32%) 0.62s 0.63s
Check Time 3.82s (± 0.46%) 3.91s (± 0.52%) +0.09s (+ 2.38%) 3.87s 3.96s
Emit Time 2.98s (± 0.69%) 2.97s (± 0.56%) -0.01s (- 0.23%) 2.94s 3.02s
Total Time 8.34s (± 0.17%) 8.42s (± 0.30%) +0.07s (+ 0.89%) 8.36s 8.46s
Angular - node (v8.9.0, x64)
Memory used 343,970k (± 0.02%) 344,830k (± 0.01%) +860k (+ 0.25%) 344,735k 344,916k
Parse Time 1.94s (± 0.39%) 1.83s (± 0.44%) -0.11s (- 5.91%) 1.81s 1.85s
Bind Time 0.82s (± 0.85%) 0.82s (± 0.75%) -0.00s (- 0.24%) 0.81s 0.84s
Check Time 5.02s (± 0.50%) 5.17s (± 0.86%) +0.14s (+ 2.83%) 5.10s 5.27s
Emit Time 6.05s (± 0.54%) 6.02s (± 1.21%) -0.03s (- 0.45%) 5.75s 6.12s
Total Time 13.83s (± 0.27%) 13.84s (± 0.60%) +0.00s (+ 0.01%) 13.54s 13.99s
Monaco - node (v8.9.0, x64)
Memory used 363,310k (± 0.02%) 363,763k (± 0.01%) +453k (+ 0.12%) 363,666k 363,845k
Parse Time 1.53s (± 0.49%) 1.43s (± 0.41%) -0.10s (- 6.27%) 1.42s 1.45s
Bind Time 0.89s (± 0.68%) 0.88s (± 1.24%) -0.01s (- 0.79%) 0.86s 0.91s
Check Time 5.27s (± 0.48%) 5.30s (± 0.88%) +0.03s (+ 0.57%) 5.15s 5.39s
Emit Time 2.92s (± 0.65%) 3.02s (± 5.39%) +0.10s (+ 3.56%) 2.89s 3.47s
Total Time 10.60s (± 0.30%) 10.64s (± 1.30%) +0.03s (+ 0.30%) 10.48s 11.05s
TFS - node (v8.9.0, x64)
Memory used 317,323k (± 0.01%) 317,773k (± 0.01%) +450k (+ 0.14%) 317,671k 317,863k
Parse Time 1.24s (± 0.52%) 1.13s (± 0.29%) -0.11s (- 8.72%) 1.12s 1.14s
Bind Time 0.67s (± 0.44%) 0.67s (± 0.51%) -0.00s (- 0.15%) 0.66s 0.67s
Check Time 4.47s (± 0.29%) 4.58s (± 1.56%) +0.11s (+ 2.44%) 4.50s 4.79s
Emit Time 3.06s (± 0.26%) 3.18s (± 2.51%) +0.12s (+ 3.86%) 2.97s 3.27s
Total Time 9.43s (± 0.21%) 9.56s (± 0.33%) +0.12s (+ 1.27%) 9.50s 9.62s
Angular - node (v8.9.0, x86)
Memory used 194,831k (± 0.02%) 195,280k (± 0.02%) +449k (+ 0.23%) 195,149k 195,372k
Parse Time 1.88s (± 0.62%) 1.78s (± 0.79%) -0.11s (- 5.73%) 1.75s 1.80s
Bind Time 0.96s (± 0.62%) 0.95s (± 0.62%) -0.00s (- 0.21%) 0.94s 0.96s
Check Time 4.61s (± 0.63%) 4.70s (± 0.54%) +0.09s (+ 1.91%) 4.64s 4.77s
Emit Time 5.83s (± 1.13%) 5.82s (± 1.08%) -0.01s (- 0.12%) 5.67s 5.94s
Total Time 13.28s (± 0.54%) 13.25s (± 0.66%) -0.03s (- 0.20%) 13.06s 13.41s
Monaco - node (v8.9.0, x86)
Memory used 202,956k (± 0.02%) 203,154k (± 0.01%) +199k (+ 0.10%) 203,098k 203,253k
Parse Time 1.59s (± 0.88%) 1.48s (± 0.40%) -0.10s (- 6.55%) 1.47s 1.50s
Bind Time 0.71s (± 0.73%) 0.71s (± 0.69%) +0.00s (+ 0.71%) 0.70s 0.72s
Check Time 4.86s (± 0.36%) 4.91s (± 0.42%) +0.05s (+ 1.07%) 4.87s 4.95s
Emit Time 3.19s (± 0.64%) 3.16s (± 0.58%) -0.03s (- 0.97%) 3.13s 3.20s
Total Time 10.34s (± 0.24%) 10.27s (± 0.22%) -0.08s (- 0.77%) 10.22s 10.31s
TFS - node (v8.9.0, x86)
Memory used 178,252k (± 0.01%) 178,511k (± 0.02%) +258k (+ 0.14%) 178,382k 178,591k
Parse Time 1.31s (± 1.10%) 1.19s (± 0.81%) -0.12s (- 9.10%) 1.18s 1.22s
Bind Time 0.63s (± 1.53%) 0.63s (± 0.54%) +0.00s (+ 0.32%) 0.63s 0.64s
Check Time 4.32s (± 0.59%) 4.38s (± 0.73%) +0.06s (+ 1.37%) 4.31s 4.46s
Emit Time 2.87s (± 0.69%) 2.84s (± 0.83%) -0.03s (- 1.05%) 2.78s 2.87s
Total Time 9.13s (± 0.48%) 9.04s (± 0.42%) -0.09s (- 0.99%) 8.93s 9.13s
Angular - node (v9.0.0, x64)
Memory used 343,585k (± 0.02%) 344,409k (± 0.02%) +824k (+ 0.24%) 344,268k 344,569k
Parse Time 1.68s (± 0.50%) 1.67s (± 0.56%) -0.01s (- 0.30%) 1.64s 1.69s
Bind Time 0.77s (± 0.62%) 0.77s (± 0.67%) -0.00s (- 0.26%) 0.76s 0.78s
Check Time 4.80s (± 0.61%) 4.88s (± 0.48%) +0.08s (+ 1.69%) 4.82s 4.92s
Emit Time 5.78s (± 1.25%) 5.60s (± 1.83%) -0.17s (- 3.03%) 5.43s 5.88s
Total Time 13.02s (± 0.44%) 12.92s (± 0.86%) -0.10s (- 0.78%) 12.74s 13.19s
Monaco - node (v9.0.0, x64)
Memory used 363,543k (± 0.07%) 363,579k (± 0.03%) +35k (+ 0.01%) 363,344k 363,723k
Parse Time 1.29s (± 0.63%) 1.27s (± 0.53%) -0.01s (- 1.01%) 1.26s 1.29s
Bind Time 0.86s (± 0.72%) 0.86s (± 0.75%) -0.00s (- 0.12%) 0.84s 0.87s
Check Time 4.89s (± 0.60%) 4.93s (± 0.47%) +0.04s (+ 0.92%) 4.89s 4.99s
Emit Time 3.35s (± 0.55%) 3.36s (± 0.44%) +0.01s (+ 0.42%) 3.34s 3.40s
Total Time 10.39s (± 0.44%) 10.43s (± 0.28%) +0.04s (+ 0.39%) 10.36s 10.51s
TFS - node (v9.0.0, x64)
Memory used 317,340k (± 0.02%) 317,551k (± 0.01%) +211k (+ 0.07%) 317,507k 317,618k
Parse Time 1.02s (± 0.51%) 1.01s (± 0.66%) -0.01s (- 1.08%) 1.00s 1.03s
Bind Time 0.61s (± 1.23%) 0.61s (± 0.95%) -0.00s (- 0.16%) 0.60s 0.63s
Check Time 4.39s (± 0.36%) 4.45s (± 0.56%) +0.06s (+ 1.30%) 4.40s 4.52s
Emit Time 3.18s (± 0.53%) 3.16s (± 1.97%) -0.02s (- 0.63%) 2.92s 3.23s
Total Time 9.21s (± 0.34%) 9.24s (± 0.88%) +0.03s (+ 0.30%) 8.93s 9.33s
Angular - node (v9.0.0, x86)
Memory used 194,919k (± 0.03%) 195,250k (± 0.03%) +332k (+ 0.17%) 195,172k 195,394k
Parse Time 1.59s (± 0.30%) 1.59s (± 0.37%) +0.00s (+ 0.00%) 1.58s 1.60s
Bind Time 0.88s (± 0.77%) 0.89s (± 0.54%) +0.00s (+ 0.34%) 0.88s 0.90s
Check Time 4.26s (± 0.66%) 4.35s (± 0.65%) +0.08s (+ 1.92%) 4.29s 4.42s
Emit Time 5.48s (± 0.49%) 5.51s (± 1.32%) +0.03s (+ 0.51%) 5.38s 5.76s
Total Time 12.21s (± 0.22%) 12.33s (± 0.80%) +0.11s (+ 0.93%) 12.17s 12.67s
Monaco - node (v9.0.0, x86)
Memory used 203,081k (± 0.02%) 203,138k (± 0.03%) +57k (+ 0.03%) 203,014k 203,233k
Parse Time 1.31s (± 0.57%) 1.30s (± 0.40%) -0.01s (- 0.91%) 1.29s 1.31s
Bind Time 0.64s (± 0.69%) 0.64s (± 0.56%) +0.00s (+ 0.31%) 0.64s 0.65s
Check Time 4.70s (± 0.51%) 4.78s (± 1.61%) +0.09s (+ 1.85%) 4.69s 5.08s
Emit Time 3.08s (± 0.87%) 3.06s (± 2.05%) -0.02s (- 0.68%) 2.82s 3.13s
Total Time 9.73s (± 0.39%) 9.79s (± 0.38%) +0.05s (+ 0.57%) 9.71s 9.86s
TFS - node (v9.0.0, x86)
Memory used 178,418k (± 0.03%) 178,478k (± 0.02%) +60k (+ 0.03%) 178,412k 178,590k
Parse Time 1.04s (± 1.31%) 1.03s (± 0.93%) -0.01s (- 1.16%) 1.01s 1.05s
Bind Time 0.58s (± 0.86%) 0.58s (± 0.63%) 0.00s ( 0.00%) 0.57s 0.58s
Check Time 4.16s (± 0.43%) 4.19s (± 0.35%) +0.03s (+ 0.72%) 4.16s 4.23s
Emit Time 2.81s (± 1.08%) 2.76s (± 1.01%) -0.05s (- 1.71%) 2.69s 2.82s
Total Time 8.58s (± 0.50%) 8.56s (± 0.43%) -0.03s (- 0.29%) 8.47s 8.62s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-142-generic
Architecturex64
Available Memory16 GB
Available Memory1 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v12.1.0, x64)
  • node (v8.9.0, x64)
  • node (v8.9.0, x86)
  • node (v9.0.0, x64)
  • node (v9.0.0, x86)
Scenarios
  • Angular - node (v12.1.0, x64)
  • Angular - node (v8.9.0, x64)
  • Angular - node (v8.9.0, x86)
  • Angular - node (v9.0.0, x64)
  • Angular - node (v9.0.0, x86)
  • Monaco - node (v12.1.0, x64)
  • Monaco - node (v8.9.0, x64)
  • Monaco - node (v8.9.0, x86)
  • Monaco - node (v9.0.0, x64)
  • Monaco - node (v9.0.0, x86)
  • TFS - node (v12.1.0, x64)
  • TFS - node (v8.9.0, x64)
  • TFS - node (v8.9.0, x86)
  • TFS - node (v9.0.0, x64)
  • TFS - node (v9.0.0, x86)
Benchmark Name Iterations
Current 32695 10
Baseline master 10

@ahejlsberg

This comment has been minimized.

Copy link
Member Author

commented Aug 9, 2019

@typescript-bot perf test this to observe effects of making CFA nodes more monomorphic.

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

commented Aug 9, 2019

Heya @ahejlsberg, I've started to run the perf test suite on this PR at d5e08d4. You can monitor the build here. It should now contribute to this PR's status checks.

Update: The results are in!

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

commented Aug 9, 2019

@ahejlsberg
The results of the perf run you requested are in!

Here they are:

Comparison Report - master..32695

Metric master 32695 Delta Best Worst
Angular - node (v12.1.0, x64)
Memory used 325,336k (± 0.05%) 327,195k (± 0.05%) +1,859k (+ 0.57%) 326,582k 327,368k
Parse Time 1.44s (± 0.40%) 1.43s (± 0.77%) -0.00s (- 0.28%) 1.41s 1.47s
Bind Time 0.76s (± 1.00%) 0.76s (± 1.02%) +0.00s (+ 0.66%) 0.75s 0.78s
Check Time 4.22s (± 0.59%) 4.33s (± 0.54%) +0.10s (+ 2.41%) 4.28s 4.38s
Emit Time 5.27s (± 0.87%) 5.23s (± 0.54%) -0.04s (- 0.68%) 5.18s 5.29s
Total Time 11.69s (± 0.37%) 11.76s (± 0.46%) +0.07s (+ 0.57%) 11.65s 11.87s
Monaco - node (v12.1.0, x64)
Memory used 345,892k (± 0.02%) 347,548k (± 0.02%) +1,656k (+ 0.48%) 347,392k 347,704k
Parse Time 1.19s (± 0.64%) 1.17s (± 0.49%) -0.02s (- 1.84%) 1.16s 1.18s
Bind Time 0.67s (± 0.96%) 0.67s (± 0.66%) 0.00s ( 0.00%) 0.66s 0.68s
Check Time 4.26s (± 0.44%) 4.34s (± 0.47%) +0.08s (+ 1.88%) 4.30s 4.38s
Emit Time 2.85s (± 0.39%) 2.85s (± 0.65%) -0.00s (- 0.04%) 2.82s 2.89s
Total Time 8.98s (± 0.31%) 9.03s (± 0.34%) +0.05s (+ 0.60%) 8.97s 9.11s
TFS - node (v12.1.0, x64)
Memory used 301,366k (± 0.03%) 303,240k (± 0.02%) +1,874k (+ 0.62%) 303,116k 303,369k
Parse Time 0.92s (± 0.76%) 0.90s (± 0.74%) -0.02s (- 1.95%) 0.89s 0.92s
Bind Time 0.62s (± 0.76%) 0.61s (± 0.56%) -0.01s (- 1.13%) 0.61s 0.62s
Check Time 3.86s (± 0.54%) 3.94s (± 0.60%) +0.09s (+ 2.28%) 3.90s 4.01s
Emit Time 2.96s (± 0.56%) 2.95s (± 0.67%) -0.01s (- 0.37%) 2.88s 2.98s
Total Time 8.36s (± 0.37%) 8.41s (± 0.38%) +0.05s (+ 0.63%) 8.30s 8.48s
Angular - node (v8.9.0, x64)
Memory used 343,952k (± 0.01%) 345,749k (± 0.02%) +1,797k (+ 0.52%) 345,639k 345,862k
Parse Time 1.95s (± 0.59%) 1.83s (± 0.57%) -0.11s (- 5.90%) 1.81s 1.86s
Bind Time 0.82s (± 0.79%) 0.81s (± 0.64%) -0.01s (- 0.86%) 0.80s 0.82s
Check Time 5.03s (± 0.72%) 5.17s (± 0.55%) +0.14s (+ 2.87%) 5.13s 5.25s
Emit Time 6.11s (± 0.60%) 6.02s (± 0.57%) -0.10s (- 1.57%) 5.96s 6.10s
Total Time 13.90s (± 0.49%) 13.83s (± 0.40%) -0.07s (- 0.52%) 13.73s 13.98s
Monaco - node (v8.9.0, x64)
Memory used 363,362k (± 0.01%) 364,897k (± 0.01%) +1,535k (+ 0.42%) 364,834k 364,965k
Parse Time 1.53s (± 0.57%) 1.43s (± 0.49%) -0.10s (- 6.48%) 1.42s 1.45s
Bind Time 0.88s (± 0.38%) 0.89s (± 1.82%) +0.01s (+ 1.36%) 0.86s 0.92s
Check Time 5.28s (± 0.59%) 5.20s (± 1.08%) -0.08s (- 1.44%) 5.10s 5.34s
Emit Time 2.93s (± 0.51%) 3.24s (± 4.71%) +0.32s (+10.83%) 2.92s 3.41s
Total Time 10.62s (± 0.35%) 10.77s (± 1.09%) +0.15s (+ 1.40%) 10.48s 10.93s
TFS - node (v8.9.0, x64)
Memory used 317,301k (± 0.01%) 319,006k (± 0.01%) +1,705k (+ 0.54%) 318,928k 319,114k
Parse Time 1.23s (± 0.28%) 1.14s (± 0.74%) -0.09s (- 7.62%) 1.12s 1.16s
Bind Time 0.67s (± 0.33%) 0.66s (± 0.76%) -0.01s (- 2.09%) 0.65s 0.67s
Check Time 4.51s (± 0.68%) 4.60s (± 1.13%) +0.10s (+ 2.13%) 4.54s 4.80s
Emit Time 3.06s (± 0.53%) 3.06s (± 1.38%) +0.01s (+ 0.16%) 2.95s 3.17s
Total Time 9.47s (± 0.36%) 9.46s (± 0.62%) -0.01s (- 0.08%) 9.35s 9.63s
Angular - node (v8.9.0, x86)
Memory used 194,825k (± 0.02%) 195,766k (± 0.03%) +941k (+ 0.48%) 195,683k 195,910k
Parse Time 1.88s (± 0.51%) 1.78s (± 0.85%) -0.10s (- 5.11%) 1.75s 1.81s
Bind Time 0.95s (± 0.59%) 0.96s (± 1.21%) +0.01s (+ 0.84%) 0.94s 0.98s
Check Time 4.60s (± 0.78%) 4.72s (± 0.55%) +0.12s (+ 2.54%) 4.66s 4.77s
Emit Time 5.85s (± 0.98%) 5.84s (± 0.99%) -0.01s (- 0.21%) 5.76s 5.99s
Total Time 13.28s (± 0.62%) 13.29s (± 0.66%) +0.02s (+ 0.14%) 13.15s 13.50s
Monaco - node (v8.9.0, x86)
Memory used 202,971k (± 0.01%) 203,742k (± 0.02%) +771k (+ 0.38%) 203,664k 203,817k
Parse Time 1.58s (± 0.60%) 1.47s (± 0.51%) -0.11s (- 6.89%) 1.46s 1.49s
Bind Time 0.71s (± 1.25%) 0.70s (± 0.63%) -0.01s (- 1.82%) 0.69s 0.71s
Check Time 4.88s (± 0.47%) 5.22s (± 0.47%) +0.35s (+ 7.09%) 5.16s 5.28s
Emit Time 3.19s (± 0.56%) 2.77s (± 1.04%) -0.43s (-13.35%) 2.72s 2.83s
Total Time 10.37s (± 0.28%) 10.17s (± 0.49%) -0.21s (- 1.98%) 10.06s 10.29s
TFS - node (v8.9.0, x86)
Memory used 178,245k (± 0.02%) 179,134k (± 0.02%) +890k (+ 0.50%) 179,054k 179,230k
Parse Time 1.30s (± 0.63%) 1.19s (± 0.47%) -0.12s (- 8.83%) 1.18s 1.20s
Bind Time 0.63s (± 1.88%) 0.62s (± 1.42%) -0.01s (- 1.90%) 0.61s 0.65s
Check Time 4.32s (± 0.61%) 4.36s (± 0.85%) +0.04s (+ 0.83%) 4.29s 4.48s
Emit Time 2.88s (± 0.88%) 2.85s (± 1.20%) -0.03s (- 1.04%) 2.78s 2.93s
Total Time 9.13s (± 0.33%) 9.01s (± 0.57%) -0.12s (- 1.35%) 8.93s 9.18s
Angular - node (v9.0.0, x64)
Memory used 343,596k (± 0.02%) 345,343k (± 0.01%) +1,747k (+ 0.51%) 345,293k 345,432k
Parse Time 1.67s (± 0.49%) 1.67s (± 0.65%) -0.00s (- 0.30%) 1.65s 1.70s
Bind Time 0.76s (± 0.48%) 0.76s (± 0.65%) +0.00s (+ 0.13%) 0.76s 0.78s
Check Time 4.78s (± 0.51%) 4.93s (± 0.46%) +0.16s (+ 3.24%) 4.89s 4.98s
Emit Time 5.68s (± 2.10%) 5.77s (± 0.32%) +0.09s (+ 1.51%) 5.74s 5.82s
Total Time 12.90s (± 0.94%) 13.13s (± 0.26%) +0.23s (+ 1.81%) 13.07s 13.24s
Monaco - node (v9.0.0, x64)
Memory used 363,462k (± 0.02%) 364,552k (± 0.01%) +1,090k (+ 0.30%) 364,444k 364,698k
Parse Time 1.29s (± 0.31%) 1.27s (± 0.70%) -0.02s (- 1.16%) 1.25s 1.29s
Bind Time 0.86s (± 0.58%) 0.84s (± 0.35%) -0.01s (- 1.64%) 0.84s 0.85s
Check Time 4.89s (± 0.50%) 5.02s (± 0.53%) +0.13s (+ 2.60%) 4.97s 5.09s
Emit Time 3.36s (± 0.59%) 3.35s (± 0.33%) -0.00s (- 0.09%) 3.34s 3.38s
Total Time 10.39s (± 0.36%) 10.49s (± 0.32%) +0.10s (+ 0.94%) 10.43s 10.59s
TFS - node (v9.0.0, x64)
Memory used 317,356k (± 0.02%) 318,790k (± 0.02%) +1,435k (+ 0.45%) 318,614k 318,913k
Parse Time 1.02s (± 0.56%) 1.01s (± 0.52%) -0.01s (- 1.08%) 1.00s 1.02s
Bind Time 0.61s (± 0.81%) 0.61s (± 0.94%) -0.01s (- 0.81%) 0.60s 0.62s
Check Time 4.38s (± 0.59%) 4.66s (± 1.62%) +0.27s (+ 6.20%) 4.47s 4.75s
Emit Time 3.20s (± 0.79%) 2.93s (± 1.45%) -0.27s (- 8.44%) 2.86s 3.04s
Total Time 9.22s (± 0.37%) 9.21s (± 0.63%) -0.01s (- 0.14%) 9.05s 9.30s
Angular - node (v9.0.0, x86)
Memory used 194,904k (± 0.02%) 195,768k (± 0.02%) +864k (+ 0.44%) 195,683k 195,873k
Parse Time 1.59s (± 0.51%) 1.59s (± 0.52%) -0.00s (- 0.25%) 1.57s 1.61s
Bind Time 0.88s (± 0.80%) 0.88s (± 0.51%) +0.00s (+ 0.11%) 0.87s 0.89s
Check Time 4.26s (± 0.48%) 4.37s (± 0.91%) +0.11s (+ 2.51%) 4.30s 4.47s
Emit Time 5.47s (± 0.33%) 5.47s (± 0.93%) +0.00s (+ 0.07%) 5.38s 5.60s
Total Time 12.21s (± 0.30%) 12.31s (± 0.71%) +0.10s (+ 0.83%) 12.13s 12.49s
Monaco - node (v9.0.0, x86)
Memory used 203,087k (± 0.03%) 203,749k (± 0.02%) +662k (+ 0.33%) 203,634k 203,839k
Parse Time 1.32s (± 0.89%) 1.31s (± 0.47%) -0.01s (- 0.91%) 1.30s 1.32s
Bind Time 0.64s (± 0.87%) 0.65s (± 0.86%) +0.01s (+ 0.93%) 0.63s 0.66s
Check Time 4.69s (± 0.46%) 4.79s (± 1.40%) +0.10s (+ 2.13%) 4.69s 5.04s
Emit Time 3.09s (± 0.52%) 3.05s (± 3.09%) -0.04s (- 1.26%) 2.68s 3.15s
Total Time 9.74s (± 0.32%) 9.79s (± 0.41%) +0.05s (+ 0.53%) 9.66s 9.87s
TFS - node (v9.0.0, x86)
Memory used 178,394k (± 0.02%) 179,136k (± 0.03%) +742k (+ 0.42%) 179,049k 179,253k
Parse Time 1.05s (± 0.91%) 1.02s (± 0.36%) -0.02s (- 2.01%) 1.02s 1.03s
Bind Time 0.57s (± 0.91%) 0.57s (± 0.70%) 0.00s ( 0.00%) 0.56s 0.58s
Check Time 4.14s (± 0.69%) 4.22s (± 0.65%) +0.08s (+ 1.88%) 4.17s 4.30s
Emit Time 2.79s (± 0.68%) 2.79s (± 0.90%) +0.00s (+ 0.04%) 2.71s 2.85s
Total Time 8.55s (± 0.37%) 8.61s (± 0.50%) +0.05s (+ 0.63%) 8.49s 8.71s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-142-generic
Architecturex64
Available Memory16 GB
Available Memory1 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v12.1.0, x64)
  • node (v8.9.0, x64)
  • node (v8.9.0, x86)
  • node (v9.0.0, x64)
  • node (v9.0.0, x86)
Scenarios
  • Angular - node (v12.1.0, x64)
  • Angular - node (v8.9.0, x64)
  • Angular - node (v8.9.0, x86)
  • Angular - node (v9.0.0, x64)
  • Angular - node (v9.0.0, x86)
  • Monaco - node (v12.1.0, x64)
  • Monaco - node (v8.9.0, x64)
  • Monaco - node (v8.9.0, x86)
  • Monaco - node (v9.0.0, x64)
  • Monaco - node (v9.0.0, x86)
  • TFS - node (v12.1.0, x64)
  • TFS - node (v8.9.0, x64)
  • TFS - node (v8.9.0, x86)
  • TFS - node (v9.0.0, x64)
  • TFS - node (v9.0.0, x86)
Benchmark Name Iterations
Current 32695 10
Baseline master 10

@ahejlsberg

This comment has been minimized.

Copy link
Member Author

commented Aug 9, 2019

@typescript-bot perf test this with less aggressive monomorphism for CFA nodes, since the previous changes just increased memory usage without improving perf.

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

commented Aug 9, 2019

Heya @ahejlsberg, I've started to run the perf test suite on this PR at 83212e7. You can monitor the build here. It should now contribute to this PR's status checks.

Update: The results are in!

@typescript-bot

This comment has been minimized.

Copy link
Collaborator

commented Aug 9, 2019

@ahejlsberg
The results of the perf run you requested are in!

Here they are:

Comparison Report - master..32695

Metric master 32695 Delta Best Worst
Angular - node (v12.1.0, x64)
Memory used 325,336k (± 0.05%) 326,300k (± 0.03%) +964k (+ 0.30%) 326,119k 326,472k
Parse Time 1.44s (± 0.40%) 1.43s (± 0.33%) -0.01s (- 0.49%) 1.42s 1.44s
Bind Time 0.76s (± 1.00%) 0.77s (± 0.76%) +0.01s (+ 1.46%) 0.75s 0.78s
Check Time 4.22s (± 0.59%) 4.30s (± 0.37%) +0.07s (+ 1.75%) 4.25s 4.33s
Emit Time 5.27s (± 0.87%) 5.22s (± 0.59%) -0.05s (- 0.89%) 5.15s 5.29s
Total Time 11.69s (± 0.37%) 11.72s (± 0.32%) +0.03s (+ 0.24%) 11.64s 11.78s
Monaco - node (v12.1.0, x64)
Memory used 345,892k (± 0.02%) 346,313k (± 0.02%) +421k (+ 0.12%) 346,141k 346,454k
Parse Time 1.19s (± 0.64%) 1.17s (± 0.58%) -0.03s (- 2.43%) 1.15s 1.18s
Bind Time 0.67s (± 0.96%) 0.67s (± 0.54%) +0.00s (+ 0.30%) 0.67s 0.68s
Check Time 4.26s (± 0.44%) 4.29s (± 0.40%) +0.03s (+ 0.82%) 4.25s 4.33s
Emit Time 2.85s (± 0.39%) 2.86s (± 0.92%) +0.01s (+ 0.21%) 2.81s 2.94s
Total Time 8.98s (± 0.31%) 8.99s (± 0.27%) +0.01s (+ 0.13%) 8.95s 9.05s
TFS - node (v12.1.0, x64)
Memory used 301,366k (± 0.03%) 301,984k (± 0.01%) +618k (+ 0.21%) 301,927k 302,083k
Parse Time 0.92s (± 0.76%) 0.90s (± 0.64%) -0.02s (- 1.95%) 0.89s 0.92s
Bind Time 0.62s (± 0.76%) 0.63s (± 1.16%) +0.01s (+ 1.29%) 0.62s 0.65s
Check Time 3.86s (± 0.54%) 3.91s (± 0.44%) +0.05s (+ 1.30%) 3.87s 3.96s
Emit Time 2.96s (± 0.56%) 2.94s (± 0.82%) -0.02s (- 0.68%) 2.89s 2.99s
Total Time 8.36s (± 0.37%) 8.38s (± 0.42%) +0.02s (+ 0.23%) 8.28s 8.43s
Angular - node (v8.9.0, x64)
Memory used 343,952k (± 0.01%) 344,860k (± 0.01%) +909k (+ 0.26%) 344,806k 344,983k
Parse Time 1.95s (± 0.59%) 1.83s (± 0.40%) -0.12s (- 5.95%) 1.81s 1.84s
Bind Time 0.82s (± 0.79%) 0.83s (± 0.60%) +0.01s (+ 0.98%) 0.81s 0.83s
Check Time 5.03s (± 0.72%) 5.17s (± 0.88%) +0.14s (+ 2.85%) 5.07s 5.29s
Emit Time 6.11s (± 0.60%) 6.01s (± 1.85%) -0.10s (- 1.65%) 5.62s 6.17s
Total Time 13.90s (± 0.49%) 13.84s (± 0.97%) -0.07s (- 0.48%) 13.35s 14.02s
Monaco - node (v8.9.0, x64)
Memory used 363,362k (± 0.01%) 363,819k (± 0.01%) +457k (+ 0.13%) 363,693k 363,932k
Parse Time 1.53s (± 0.57%) 1.43s (± 0.49%) -0.10s (- 6.48%) 1.41s 1.44s
Bind Time 0.88s (± 0.38%) 0.89s (± 1.07%) +0.01s (+ 0.68%) 0.87s 0.91s
Check Time 5.28s (± 0.59%) 5.26s (± 1.39%) -0.01s (- 0.28%) 5.11s 5.37s
Emit Time 2.93s (± 0.51%) 3.09s (± 5.78%) +0.17s (+ 5.67%) 2.90s 3.50s
Total Time 10.62s (± 0.35%) 10.67s (± 1.18%) +0.05s (+ 0.50%) 10.49s 10.96s
TFS - node (v8.9.0, x64)
Memory used 317,301k (± 0.01%) 317,783k (± 0.01%) +482k (+ 0.15%) 317,700k 317,863k
Parse Time 1.23s (± 0.28%) 1.14s (± 0.44%) -0.10s (- 7.95%) 1.12s 1.14s
Bind Time 0.67s (± 0.33%) 0.67s (± 0.33%) -0.00s (- 0.00%) 0.66s 0.67s
Check Time 4.51s (± 0.68%) 4.59s (± 1.38%) +0.09s (+ 1.93%) 4.50s 4.80s
Emit Time 3.06s (± 0.53%) 3.17s (± 2.47%) +0.11s (+ 3.47%) 3.01s 3.29s
Total Time 9.47s (± 0.36%) 9.56s (± 0.96%) +0.09s (+ 0.98%) 9.39s 9.80s
Angular - node (v8.9.0, x86)
Memory used 194,825k (± 0.02%) 195,274k (± 0.02%) +449k (+ 0.23%) 195,175k 195,382k
Parse Time 1.88s (± 0.51%) 1.77s (± 0.54%) -0.10s (- 5.59%) 1.76s 1.80s
Bind Time 0.95s (± 0.59%) 0.96s (± 1.19%) +0.01s (+ 1.05%) 0.93s 0.98s
Check Time 4.60s (± 0.78%) 4.68s (± 0.79%) +0.08s (+ 1.65%) 4.62s 4.80s
Emit Time 5.85s (± 0.98%) 5.85s (± 1.19%) -0.00s (- 0.05%) 5.69s 5.98s
Total Time 13.28s (± 0.62%) 13.25s (± 0.72%) -0.02s (- 0.17%) 13.04s 13.50s
Monaco - node (v8.9.0, x86)
Memory used 202,971k (± 0.01%) 203,152k (± 0.01%) +181k (+ 0.09%) 203,102k 203,210k
Parse Time 1.58s (± 0.60%) 1.49s (± 0.68%) -0.10s (- 6.19%) 1.47s 1.51s
Bind Time 0.71s (± 1.25%) 0.71s (± 0.63%) -0.01s (- 0.98%) 0.70s 0.72s
Check Time 4.88s (± 0.47%) 4.92s (± 0.81%) +0.04s (+ 0.82%) 4.84s 5.01s
Emit Time 3.19s (± 0.56%) 3.16s (± 0.73%) -0.03s (- 0.88%) 3.10s 3.21s
Total Time 10.37s (± 0.28%) 10.28s (± 0.59%) -0.10s (- 0.92%) 10.12s 10.40s
TFS - node (v8.9.0, x86)
Memory used 178,245k (± 0.02%) 178,491k (± 0.03%) +247k (+ 0.14%) 178,331k 178,551k
Parse Time 1.30s (± 0.63%) 1.19s (± 0.84%) -0.11s (- 8.44%) 1.18s 1.22s
Bind Time 0.63s (± 1.88%) 0.63s (± 1.30%) +0.00s (+ 0.32%) 0.62s 0.66s
Check Time 4.32s (± 0.61%) 4.35s (± 0.74%) +0.03s (+ 0.72%) 4.31s 4.44s
Emit Time 2.88s (± 0.88%) 2.86s (± 1.13%) -0.02s (- 0.63%) 2.82s 2.98s
Total Time 9.13s (± 0.33%) 9.04s (± 0.66%) -0.09s (- 1.03%) 8.96s 9.22s
Angular - node (v9.0.0, x64)
Memory used 343,596k (± 0.02%) 344,417k (± 0.01%) +821k (+ 0.24%) 344,349k 344,518k
Parse Time 1.67s (± 0.49%) 1.67s (± 0.39%) -0.00s (- 0.06%) 1.65s 1.68s
Bind Time 0.76s (± 0.48%) 0.77s (± 0.62%) +0.00s (+ 0.39%) 0.76s 0.78s
Check Time 4.78s (± 0.51%) 4.89s (± 0.47%) +0.11s (+ 2.34%) 4.84s 4.94s
Emit Time 5.68s (± 2.10%) 5.60s (± 2.23%) -0.08s (- 1.44%) 5.40s 5.89s
Total Time 12.90s (± 0.94%) 12.93s (± 0.93%) +0.03s (+ 0.26%) 12.74s 13.19s
Monaco - node (v9.0.0, x64)
Memory used 363,462k (± 0.02%) 363,611k (± 0.03%) +149k (+ 0.04%) 363,261k 363,770k
Parse Time 1.29s (± 0.31%) 1.27s (± 0.29%) -0.01s (- 1.09%) 1.27s 1.28s
Bind Time 0.86s (± 0.58%) 0.86s (± 0.78%) +0.00s (+ 0.12%) 0.84s 0.87s
Check Time 4.89s (± 0.50%) 4.93s (± 0.42%) +0.04s (+ 0.74%) 4.89s 4.99s
Emit Time 3.36s (± 0.59%) 3.36s (± 0.26%) +0.00s (+ 0.03%) 3.34s 3.37s
Total Time 10.39s (± 0.36%) 10.42s (± 0.20%) +0.03s (+ 0.28%) 10.38s 10.48s
TFS - node (v9.0.0, x64)
Memory used 317,356k (± 0.02%) 317,551k (± 0.01%) +196k (+ 0.06%) 317,474k 317,645k
Parse Time 1.02s (± 0.56%) 1.01s (± 0.40%) -0.01s (- 0.88%) 1.00s 1.02s
Bind Time 0.61s (± 0.81%) 0.62s (± 0.80%) +0.00s (+ 0.16%) 0.61s 0.63s
Check Time 4.38s (± 0.59%) 4.46s (± 0.45%) +0.07s (+ 1.71%) 4.42s 4.51s
Emit Time 3.20s (± 0.79%) 3.19s (± 0.42%) -0.01s (- 0.31%) 3.17s 3.23s
Total Time 9.22s (± 0.37%) 9.28s (± 0.39%) +0.06s (+ 0.64%) 9.22s 9.36s
Angular - node (v9.0.0, x86)
Memory used 194,904k (± 0.02%) 195,317k (± 0.04%) +413k (+ 0.21%) 195,138k 195,470k
Parse Time 1.59s (± 0.51%) 1.59s (± 0.55%) 0.00s ( 0.00%) 1.57s 1.61s
Bind Time 0.88s (± 0.80%) 0.89s (± 0.77%) +0.01s (+ 0.57%) 0.88s 0.91s
Check Time 4.26s (± 0.48%) 4.35s (± 0.33%) +0.09s (+ 2.06%) 4.32s 4.39s
Emit Time 5.47s (± 0.33%) 5.48s (± 0.71%) +0.01s (+ 0.24%) 5.42s 5.60s
Total Time 12.21s (± 0.30%) 12.31s (± 0.34%) +0.10s (+ 0.82%) 12.23s 12.41s
Monaco - node (v9.0.0, x86)
Memory used 203,087k (± 0.03%) 203,130k (± 0.02%) +43k (+ 0.02%) 203,050k 203,217k
Parse Time 1.32s (± 0.89%) 1.31s (± 0.89%) -0.02s (- 1.14%) 1.29s 1.34s
Bind Time 0.64s (± 0.87%) 0.64s (± 0.76%) +0.00s (+ 0.31%) 0.64s 0.66s
Check Time 4.69s (± 0.46%) 4.73s (± 0.32%) +0.04s (+ 0.81%) 4.70s 4.77s
Emit Time 3.09s (± 0.52%) 3.12s (± 0.39%) +0.03s (+ 0.81%) 3.09s 3.14s
Total Time 9.74s (± 0.32%) 9.79s (± 0.30%) +0.05s (+ 0.51%) 9.73s 9.87s
TFS - node (v9.0.0, x86)
Memory used 178,394k (± 0.02%) 178,506k (± 0.03%) +111k (+ 0.06%) 178,423k 178,640k
Parse Time 1.05s (± 0.91%) 1.03s (± 1.07%) -0.01s (- 1.15%) 1.01s 1.06s
Bind Time 0.57s (± 0.91%) 0.58s (± 1.01%) +0.01s (+ 1.05%) 0.56s 0.59s
Check Time 4.14s (± 0.69%) 4.23s (± 0.85%) +0.09s (+ 2.13%) 4.18s 4.35s
Emit Time 2.79s (± 0.68%) 2.79s (± 1.06%) -0.01s (- 0.21%) 2.69s 2.84s
Total Time 8.55s (± 0.37%) 8.63s (± 0.65%) +0.07s (+ 0.87%) 8.47s 8.72s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-142-generic
Architecturex64
Available Memory16 GB
Available Memory1 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v12.1.0, x64)
  • node (v8.9.0, x64)
  • node (v8.9.0, x86)
  • node (v9.0.0, x64)
  • node (v9.0.0, x86)
Scenarios
  • Angular - node (v12.1.0, x64)
  • Angular - node (v8.9.0, x64)
  • Angular - node (v8.9.0, x86)
  • Angular - node (v9.0.0, x64)
  • Angular - node (v9.0.0, x86)
  • Monaco - node (v12.1.0, x64)
  • Monaco - node (v8.9.0, x64)
  • Monaco - node (v8.9.0, x86)
  • Monaco - node (v9.0.0, x64)
  • Monaco - node (v9.0.0, x86)
  • TFS - node (v12.1.0, x64)
  • TFS - node (v8.9.0, x64)
  • TFS - node (v8.9.0, x86)
  • TFS - node (v9.0.0, x64)
  • TFS - node (v9.0.0, x86)
Benchmark Name Iterations
Current 32695 10
Baseline master 10

ahejlsberg added some commits Aug 9, 2019

Merge branch 'master' into assertionsInControlFlow
# Conflicts:
#	tests/baselines/reference/api/tsserverlibrary.d.ts
#	tests/baselines/reference/api/typescript.d.ts
@magnushiie

This comment has been minimized.

Copy link
Contributor

commented Aug 12, 2019

Could this somehow be unified with #7481? It feels kind of the same thing, just in expression context rather than statement context.

@jack-williams

This comment has been minimized.

Copy link
Collaborator

commented Aug 17, 2019

While the behaivour is clearly specified in the PR notes, I wonder how many people are going to be bitten by missing annotations:

interface Foo<T extends number> {
    value: T;
    assertZero(): asserts this is Foo<0>;
}

function example<T extends number>(choice: boolean, left: Foo<T>, right: Foo<T>): 0 {
    const picked = choice ? left : right;
    picked.assertZero();
    return picked.value; // error, why?.
}

Is there anyway to help guide users to the right solution?

const picked: Foo<T> = choice ? left : right;

When checking a call expression is it easy (and cheap) to check whether the call returns an assertion predicate but the call is not bound in the CFG? It might be nice to have some warning or lint rule that flags assertion calls with no effect.

@rbuckton

This comment has been minimized.

Copy link
Member

commented Aug 22, 2019

We discussed in a design meeting the possibility of a function return type defining both an assertion as well as a regular return type. I may have a use case where we could leverage this.

We recently introduced stricter Generator type inference and the Generator<T, TReturn, TNext> type, which dramatically improves our ability to strongly type generator functions, however we still lack the ability to model a type for "generator trampolines" like co or redux-saga. This deficiency is due to the fact that we currently have no way to indicate that the return type of one call to generator.next() should flow into the argument type of a subsequenct call to generator.next(value).

If we introduce the ability to apply both an assertion and a return type, then we get closer to solving this deficiency:

// initial state
interface Start<TReturn> {
    next<T>(): IteratorResult<T, TReturn>
        & asserts this is Resume<TResult, T>;
}

// subsequent state
interface Resume<TResult, TNext> {
    next<T>(next: TNext): IteratorResult<T, TReturn>
        & asserts this is Resume<TResult, T>;
}

function trampoline<TResult>(gen: Start<TResult>) {
    let result = gen.next<A>();
    // result is `IteratorResult<A, TResult>`
    // gen is `Resume<TResult, A>`
    while (!result.done) {
        // first time: result.value is `A` and `TNext` is `A`
        // second time: result.value is `B` and `TNext` is `B`
        result = gen.next<B>(result.value);
        // result is `IteratorResult<B, TResult>`
        // gen is `Resume<TResult, B>`
    }
    return result.value;
}

trampoline(function * () {
    let a: A = ...;
    let b: B = yield a;
});

For example, an async trampoline could be defined as follows:

type AwaiterResult<T, TReturn> =
    IteratorResult<PromiseLike<T> | T, PromiseLike<TReturn> | TReturn>;

interface AsyncStart<TReturn> {
    next<T>(): AwaiterResult<T, TReturn> & asserts this is AsyncResume<TReturn, T>;
}

interface AsyncResume<TReturn, TNext> {
    next<T>(value: TNext): AwaiterResult<T, TReturn> & asserts this is AsyncResume<TReturn, T>;
}

declare function co<TReturn>(gen: AsyncStart<TReturn>): Promise<TReturn>;

co(function* gen() {
    let x = yield Promise.resolve(1);
    // x is `number`

    let y = yield "hi";
    // y is `string`
});
@Jessidhia

This comment has been minimized.

Copy link

commented Aug 22, 2019

That almost seems like an obvious improvement in retrospect; not even with generators, but I remember having difficulty writing correct types for a pattern where a "self-modifying this" would have helped immensely.

I can't remember what specifically it was, though 😓

@Igorbek

This comment has been minimized.

Copy link
Contributor

commented Aug 22, 2019

@rbuckton I came here with the intention to write almost exactly what you did! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.