Typescript: Weaken type checks based on CFA with assertion conditions

Created on 8 Dec 2019  路  6Comments  路  Source: microsoft/TypeScript

Too strict for tests.

cc @ahejlsberg


TypeScript Version: 3.7.x-dev.20191207


Search Terms:

Code

declare function assert(value: any): asserts value;
declare const set: Set<0>;

assert(set.size === 0);
set.add(0);
assert(set.size === 1);

Expected behavior:
pass
Actual behavior:
This condition will always return 'false' since the types '0' and '1' have no overlap.(2367)
Playground Link: http://www.typescriptlang.org/play/index.html?ssl=1&ssc=1&pln=6&pc=24#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXygGdCQYMAKANygmRAC4DUBPASkaJLMPmtpADcAKFCRYCMHkIZ4JDIwDKIDAB4ADAD5hQzqQpyAdISwAvBAF5L8Na2GGowYORvDdZcoeNn4l8-ACMtkA

Related Issues:

Needs More Info

Most helpful comment

Let's imagine that we disable overlap detection. Then what do you expect set.size to be at the end of your example code? I think it would end up looking like this:

declare function guard<T>(x: any, v: T): x is T;
declare const set: Set<0>;
if (!guard(set.size, 0 as const)) throw new Error(); // assert(set.size === 0)
if (!guard(set.size, 1 as const)) throw new Error(); // assert(set.size === 1)
set.size; // <--- never

Unless you want to narrow to never, I suspect you are using asserts functions in places you shouldn't. Perhaps you want two assert() functions, one which performs CFA narrowing and one which doesn't:

// keep using this one for your tests
function assert(x: any) {
    if (!x) throw new Error();
}

// use this one for things you actually want to narrow
function assertCFA(x: any): asserts x {
    assert(x);
}

Or, is there some explicit suggestion here for how to modify the behavior of TS assertion functions so that they behave well for your use case without disabling CFA narrowing entirely?

All 6 comments

Feels like a duplicate of #9998 if you expect set.add(0) to widen set.size from 0 back to number.

It is the root issue but this issue addresses a new problem caused by a new feature asserts verb. That issue did know neither this future problem nor asserts verb. And since this issue is scoped by the "asserts verb" new feature, this issue can be resolved separately.

@falsandtru I don't understand what the proposal here is. Please be more descriptive; I know you are capable of it.

I'm just explaining the problem here. But if the compiler resolves the problem, the compiler has to disable overlap checks using CFA such as TS2367 only with assertion functions using asserts verb.

The most fatal problem is introducing new assertion functions using asserts verb breaks a lot of tests of a lot of projects. The following type errors are the result of introducing the asserts verb and the new assertion function to https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/power-assert and https://github.com/falsandtru/spica:

TypeScript error: src/cache.test.ts(69,40): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/cache.test.ts(77,40): Error TS2367: This condition will always return 'false' since the types '0' and '2' have no overlap.
TypeScript error: src/cache.test.ts(85,40): Error TS2367: This condition will always return 'false' since the types '0' and '3' have no overlap.
TypeScript error: src/cache.test.ts(108,40): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/cache.test.ts(115,40): Error TS2367: This condition will always return 'false' since the types '0' and '2' have no overlap.
TypeScript error: src/cache.test.ts(121,40): Error TS2367: This condition will always return 'false' since the types '0' and '3' have no overlap.
TypeScript error: src/cache.test.ts(145,40): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/cache.test.ts(148,40): Error TS2367: This condition will always return 'false' since the types '0' and '2' have no overlap.
TypeScript error: src/cache.test.ts(150,40): Error TS2367: This condition will always return 'false' since the types '0' and '3' have no overlap.
TypeScript error: src/cache.test.ts(157,40): Error TS2367: This condition will always return 'false' since the types '0' and '5' have no overlap.
TypeScript error: src/collection/datamap.test.ts(79,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/collection/datamap.test.ts(81,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/collection/datamap.test.ts(83,14): Error TS2367: This condition will always return 'false' since the types '0' and '2' have no overlap.
TypeScript error: src/collection/datamap.test.ts(86,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/coroutine.test.ts(64,14): Error TS2367: This condition will always return 'false' since the types '2' and '4' have no overlap.
TypeScript error: src/coroutine.test.ts(76,14): Error TS2367: This condition will always return 'false' since the types '1' and '3' have no overlap.
TypeScript error: src/coroutine.test.ts(80,14): Error TS2367: This condition will always return 'false' since the types '3' and '4' have no overlap.
TypeScript error: src/supervisor.test.ts(30,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(37,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(54,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(55,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(59,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(80,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(91,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(92,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(94,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(196,14): Error TS2367: This condition will always return 'false' since the types '0' and '4' have no overlap.
TypeScript error: src/supervisor.test.ts(272,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(276,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(458,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(461,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(462,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(464,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(465,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(467,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(481,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(485,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(486,14): Error TS2367: This condition will always return 'false' since the types '0' and '2' have no overlap.
TypeScript error: src/supervisor.test.ts(488,14): Error TS2367: This condition will always return 'false' since the types '0' and '1' have no overlap.
TypeScript error: src/supervisor.test.ts(496,14): Error TS2367: This condition will always return 'false' since the types '0' and '2' have no overlap.
TypeScript error: src/url.test.ts(71,14): Error TS2367: This condition will always return 'false' since the types 'Path<"https://example.com">' and '"/"' have no overlap.
TypeScript error: src/url.test.ts(77,14): Error TS2367: This condition will always return 'false' since the types 'Pathname<"https://example.com">' and '"/"' have no overlap.

A lot of developers will meet the same or similar errors. So I believe it is currently impossible to actually use the new assertion functions.

@ahejlsberg Can you make your new assertion functions usable for the existing tests? They don't support some typical test patterns. Otherwise I have to revert the introduction of them.

Let's imagine that we disable overlap detection. Then what do you expect set.size to be at the end of your example code? I think it would end up looking like this:

declare function guard<T>(x: any, v: T): x is T;
declare const set: Set<0>;
if (!guard(set.size, 0 as const)) throw new Error(); // assert(set.size === 0)
if (!guard(set.size, 1 as const)) throw new Error(); // assert(set.size === 1)
set.size; // <--- never

Unless you want to narrow to never, I suspect you are using asserts functions in places you shouldn't. Perhaps you want two assert() functions, one which performs CFA narrowing and one which doesn't:

// keep using this one for your tests
function assert(x: any) {
    if (!x) throw new Error();
}

// use this one for things you actually want to narrow
function assertCFA(x: any): asserts x {
    assert(x);
}

Or, is there some explicit suggestion here for how to modify the behavior of TS assertion functions so that they behave well for your use case without disabling CFA narrowing entirely?

Was this page helpful?
0 / 5 - 0 ratings