Skip to content

Commit 4110b80

Browse files
authoredSep 14, 2022
Fix equality narrowing and comparable relation for intersections with {} (#50735)
* Fox equality narrowing and comparable relation for intersections with {} * Accept new baselines * Add tests * Accept new baselines
1 parent b23f1d6 commit 4110b80

8 files changed

+849
-26
lines changed
 

‎src/compiler/checker.ts

+18-22
Original file line numberDiff line numberDiff line change
@@ -19216,11 +19216,15 @@ namespace ts {
1921619216
// parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't
1921719217
// appear to be comparable to '2'.
1921819218
if (relation === comparableRelation && target.flags & TypeFlags.Primitive) {
19219-
const constraints = sameMap((source as IntersectionType).types, getBaseConstraintOrType);
19219+
const constraints = sameMap((source as IntersectionType).types, t => t.flags & TypeFlags.Instantiable ? getBaseConstraintOfType(t) || unknownType : t);
1922019220
if (constraints !== (source as IntersectionType).types) {
1922119221
source = getIntersectionType(constraints);
19222+
if (source.flags & TypeFlags.Never) {
19223+
return Ternary.False;
19224+
}
1922219225
if (!(source.flags & TypeFlags.Intersection)) {
19223-
return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false);
19226+
return isRelatedTo(source, target, RecursionFlags.Source, /*reportErrors*/ false) ||
19227+
isRelatedTo(target, source, RecursionFlags.Source, /*reportErrors*/ false);
1922419228
}
1922519229
}
1922619230
}
@@ -19541,7 +19545,7 @@ namespace ts {
1954119545
// the type param can be compared with itself in the target (with the influence of its constraint to match other parts)
1954219546
// For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)`
1954319547
const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source as IntersectionType).types: [source], !!(target.flags & TypeFlags.Union));
19544-
if (constraint && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself
19548+
if (constraint && !(constraint.flags & TypeFlags.Never) && everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself
1954519549
// TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this
1954619550
result = isRelatedTo(constraint, target, RecursionFlags.Source, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState);
1954719551
}
@@ -25322,25 +25326,11 @@ namespace ts {
2532225326
assumeTrue = !assumeTrue;
2532325327
}
2532425328
const valueType = getTypeOfExpression(value);
25325-
if (((type.flags & TypeFlags.Unknown) || isEmptyAnonymousObjectType(type) && !(valueType.flags & TypeFlags.Nullable)) &&
25326-
assumeTrue &&
25327-
(operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)
25328-
) {
25329-
if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) {
25330-
return valueType;
25331-
}
25332-
if (valueType.flags & TypeFlags.Object) {
25333-
return nonPrimitiveType;
25334-
}
25335-
if (type.flags & TypeFlags.Unknown) {
25336-
return type;
25337-
}
25338-
}
25329+
const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
2533925330
if (valueType.flags & TypeFlags.Nullable) {
2534025331
if (!strictNullChecks) {
2534125332
return type;
2534225333
}
25343-
const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken;
2534425334
const facts = doubleEquals ?
2534525335
assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull :
2534625336
valueType.flags & TypeFlags.Null ?
@@ -25349,10 +25339,16 @@ namespace ts {
2534925339
return getAdjustedTypeWithFacts(type, facts);
2535025340
}
2535125341
if (assumeTrue) {
25352-
const filterFn: (t: Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ?
25353-
t => areTypesComparable(t, valueType) || isCoercibleUnderDoubleEquals(t, valueType) :
25354-
t => areTypesComparable(t, valueType);
25355-
return replacePrimitivesWithLiterals(filterType(type, filterFn), valueType);
25342+
if (!doubleEquals && (type.flags & TypeFlags.Unknown || someType(type, isEmptyAnonymousObjectType))) {
25343+
if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) || isEmptyAnonymousObjectType(valueType)) {
25344+
return valueType;
25345+
}
25346+
if (valueType.flags & TypeFlags.Object) {
25347+
return nonPrimitiveType;
25348+
}
25349+
}
25350+
const filteredType = filterType(type, t => areTypesComparable(t, valueType) || doubleEquals && isCoercibleUnderDoubleEquals(t, valueType));
25351+
return replacePrimitivesWithLiterals(filteredType, valueType);
2535625352
}
2535725353
if (isUnitType(valueType)) {
2535825354
return filterType(type, t => !(isUnitLikeType(t) && areTypesComparable(t, valueType)));

‎tests/baselines/reference/controlFlowOptionalChain.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -1243,9 +1243,9 @@ function f15(o: Thing | undefined, value: number) {
12431243
}
12441244
else {
12451245
o.foo;
1246-
>o.foo : number
1246+
>o.foo : string | number
12471247
>o : Thing
1248-
>foo : number
1248+
>foo : string | number
12491249
}
12501250
}
12511251

‎tests/baselines/reference/intersectionNarrowing.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ function f4<T>(x: T & 1 | T & 2) {
5959

6060
case 1: x; break; // T & 1
6161
>1 : 1
62-
>x : (T & 1) | (T & 2)
62+
>x : T & 1
6363

6464
case 2: x; break; // T & 2
6565
>2 : 2
66-
>x : (T & 1) | (T & 2)
66+
>x : T & 2
6767

6868
default: x; // Should narrow to never
6969
>x : never

‎tests/baselines/reference/unknownControlFlow.errors.txt

+101
Original file line numberDiff line numberDiff line change
@@ -316,4 +316,105 @@ tests/cases/conformance/types/unknown/unknownControlFlow.ts(293,5): error TS2345
316316
type NullableFoo = Foo | undefined;
317317

318318
type Bar<T extends NullableFoo> = NonNullable<T>[string];
319+
320+
// Generics and intersections with {}
321+
322+
function fx0<T>(value: T & ({} | null)) {
323+
if (value === 42) {
324+
value; // T & {}
325+
}
326+
else {
327+
value; // T & ({} | null)
328+
}
329+
}
330+
331+
function fx1<T extends unknown>(value: T & ({} | null)) {
332+
if (value === 42) {
333+
value; // T & {}
334+
}
335+
else {
336+
value; // T & ({} | null)
337+
}
338+
}
339+
340+
function fx2<T extends {}>(value: T & ({} | null)) {
341+
if (value === 42) {
342+
value; // T & {}
343+
}
344+
else {
345+
value; // T & ({} | null)
346+
}
347+
}
348+
349+
function fx3<T extends {} | undefined>(value: T & ({} | null)) {
350+
if (value === 42) {
351+
value; // T & {}
352+
}
353+
else {
354+
value; // T & ({} | null)
355+
}
356+
}
357+
358+
function fx4<T extends {} | null>(value: T & ({} | null)) {
359+
if (value === 42) {
360+
value; // T & {}
361+
}
362+
else {
363+
value; // T & ({} | null)
364+
}
365+
}
366+
367+
function fx5<T extends {} | null | undefined>(value: T & ({} | null)) {
368+
if (value === 42) {
369+
value; // T & {}
370+
}
371+
else {
372+
value; // T & ({} | null)
373+
}
374+
}
375+
376+
// Double-equals narrowing
377+
378+
function fx10(x: string | number, y: number) {
379+
if (x == y) {
380+
x; // string | number
381+
}
382+
else {
383+
x; // string | number
384+
}
385+
if (x != y) {
386+
x; // string | number
387+
}
388+
else {
389+
x; // string | number
390+
}
391+
}
392+
393+
// Repros from #50706
394+
395+
function SendBlob(encoding: unknown) {
396+
if (encoding !== undefined && encoding !== 'utf8') {
397+
throw new Error('encoding');
398+
}
399+
encoding;
400+
};
401+
402+
function doSomething1<T extends unknown>(value: T): T {
403+
if (value === undefined) {
404+
return value;
405+
}
406+
if (value === 42) {
407+
throw Error('Meaning of life value');
408+
}
409+
return value;
410+
}
411+
412+
function doSomething2(value: unknown): void {
413+
if (value === undefined) {
414+
return;
415+
}
416+
if (value === 42) {
417+
value;
418+
}
419+
}
319420

0 commit comments

Comments
 (0)
Please sign in to comment.