Skip to content

Commit 2f0c607

Browse files
authored
Properly handle missingType in intersections (microsoft#46052)
* Properly handle missingType in intersections * Add regression tests * Accept new baselines * Fix tests
1 parent 2be3d45 commit 2f0c607

7 files changed

+188
-18
lines changed

src/compiler/checker.ts

+28-17
Original file line numberDiff line numberDiff line change
@@ -14030,7 +14030,6 @@ namespace ts {
1403014030
// We ignore 'never' types in unions
1403114031
if (!(flags & TypeFlags.Never)) {
1403214032
includes |= flags & TypeFlags.IncludesMask;
14033-
if (flags & TypeFlags.StructuredOrInstantiable) includes |= TypeFlags.IncludesStructuredOrInstantiable;
1403414033
if (type === wildcardType) includes |= TypeFlags.IncludesWildcard;
1403514034
if (!strictNullChecks && flags & TypeFlags.Nullable) {
1403614035
if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType;
@@ -14337,13 +14336,19 @@ namespace ts {
1433714336
if (flags & TypeFlags.AnyOrUnknown) {
1433814337
if (type === wildcardType) includes |= TypeFlags.IncludesWildcard;
1433914338
}
14340-
else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !typeSet.has(type.id.toString())) {
14341-
if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) {
14342-
// We have seen two distinct unit types which means we should reduce to an
14343-
// empty intersection. Adding TypeFlags.NonPrimitive causes that to happen.
14344-
includes |= TypeFlags.NonPrimitive;
14339+
else if (strictNullChecks || !(flags & TypeFlags.Nullable)) {
14340+
if (exactOptionalPropertyTypes && type === missingType) {
14341+
includes |= TypeFlags.IncludesMissingType;
14342+
type = undefinedType;
14343+
}
14344+
if (!typeSet.has(type.id.toString())) {
14345+
if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) {
14346+
// We have seen two distinct unit types which means we should reduce to an
14347+
// empty intersection. Adding TypeFlags.NonPrimitive causes that to happen.
14348+
includes |= TypeFlags.NonPrimitive;
14349+
}
14350+
typeSet.set(type.id.toString(), type);
1434514351
}
14346-
typeSet.set(type.id.toString(), type);
1434714352
}
1434814353
includes |= flags & TypeFlags.IncludesMask;
1434914354
}
@@ -14418,14 +14423,14 @@ namespace ts {
1441814423
return false;
1441914424
}
1442014425

14421-
function extractIrreducible(types: Type[], flag: TypeFlags) {
14422-
if (every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag)))) {
14423-
for (let i = 0; i < types.length; i++) {
14424-
types[i] = filterType(types[i], t => !(t.flags & flag));
14425-
}
14426-
return true;
14426+
function eachIsUnionContaining(types: Type[], flag: TypeFlags) {
14427+
return every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag)));
14428+
}
14429+
14430+
function removeFromEach(types: Type[], flag: TypeFlags) {
14431+
for (let i = 0; i < types.length; i++) {
14432+
types[i] = filterType(types[i], t => !(t.flags & flag));
1442714433
}
14428-
return false;
1442914434
}
1443014435

1443114436
// If the given list of types contains more than one union of primitive types, replace the
@@ -14535,6 +14540,9 @@ namespace ts {
1453514540
if (includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.Object) {
1453614541
orderedRemoveItemAt(typeSet, findIndex(typeSet, isEmptyAnonymousObjectType));
1453714542
}
14543+
if (includes & TypeFlags.IncludesMissingType) {
14544+
typeSet[typeSet.indexOf(undefinedType)] = missingType;
14545+
}
1453814546
if (typeSet.length === 0) {
1453914547
return unknownType;
1454014548
}
@@ -14551,10 +14559,13 @@ namespace ts {
1455114559
// reduced we'll never reduce again, so this occurs at most once.
1455214560
result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments);
1455314561
}
14554-
else if (extractIrreducible(typeSet, TypeFlags.Undefined)) {
14555-
result = getUnionType([getIntersectionType(typeSet), undefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
14562+
else if (eachIsUnionContaining(typeSet, TypeFlags.Undefined)) {
14563+
const undefinedOrMissingType = exactOptionalPropertyTypes && some(typeSet, t => containsType((t as UnionType).types, missingType)) ? missingType : undefinedType;
14564+
removeFromEach(typeSet, TypeFlags.Undefined);
14565+
result = getUnionType([getIntersectionType(typeSet), undefinedOrMissingType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
1455614566
}
14557-
else if (extractIrreducible(typeSet, TypeFlags.Null)) {
14567+
else if (eachIsUnionContaining(typeSet, TypeFlags.Null)) {
14568+
removeFromEach(typeSet, TypeFlags.Null);
1455814569
result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
1455914570
}
1456014571
else {

src/compiler/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5181,7 +5181,7 @@ namespace ts {
51815181
IncludesMask = Any | Unknown | Primitive | Never | Object | Union | Intersection | NonPrimitive | TemplateLiteral,
51825182
// The following flags are used for different purposes during union and intersection type construction
51835183
/* @internal */
5184-
IncludesStructuredOrInstantiable = TypeParameter,
5184+
IncludesMissingType = TypeParameter,
51855185
/* @internal */
51865186
IncludesNonWideningType = Index,
51875187
/* @internal */

tests/baselines/reference/strictOptionalProperties1.errors.txt

+23
Original file line numberDiff line numberDiff line change
@@ -329,4 +329,27 @@ tests/cases/compiler/strictOptionalProperties1.ts(211,1): error TS2322: Type 'st
329329
~
330330
!!! error TS2322: Type 'string | boolean | undefined' is not assignable to type '{ [x: string]: string | number; }'.
331331
!!! error TS2322: Type 'undefined' is not assignable to type '{ [x: string]: string | number; }'.
332+
333+
// Repro from #46004
334+
335+
interface PropsFromReact {
336+
onClick?: () => void;
337+
}
338+
339+
interface PropsFromMaterialUI {
340+
onClick?: (() => void) | undefined;
341+
}
342+
343+
type TheTypeFromMaterialUI = PropsFromReact & PropsFromMaterialUI;
344+
345+
interface NavBottomListItem extends TheTypeFromMaterialUI {
346+
value: string;
347+
}
348+
349+
// Repro from #46004
350+
351+
type UA = undefined; // Explicit undefined type
352+
type UB = { x?: never }['x']; // undefined from missing property
353+
354+
type UC = UA & UB; // undefined
332355

tests/baselines/reference/strictOptionalProperties1.js

+38
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,29 @@ a = b;
210210
a = c;
211211
a = d; // Error
212212
a = e; // Error
213+
214+
// Repro from #46004
215+
216+
interface PropsFromReact {
217+
onClick?: () => void;
218+
}
219+
220+
interface PropsFromMaterialUI {
221+
onClick?: (() => void) | undefined;
222+
}
223+
224+
type TheTypeFromMaterialUI = PropsFromReact & PropsFromMaterialUI;
225+
226+
interface NavBottomListItem extends TheTypeFromMaterialUI {
227+
value: string;
228+
}
229+
230+
// Repro from #46004
231+
232+
type UA = undefined; // Explicit undefined type
233+
type UB = { x?: never }['x']; // undefined from missing property
234+
235+
type UC = UA & UB; // undefined
213236

214237

215238
//// [strictOptionalProperties1.js]
@@ -453,3 +476,18 @@ declare var e: {
453476
a: number;
454477
b?: string | undefined;
455478
};
479+
interface PropsFromReact {
480+
onClick?: () => void;
481+
}
482+
interface PropsFromMaterialUI {
483+
onClick?: (() => void) | undefined;
484+
}
485+
declare type TheTypeFromMaterialUI = PropsFromReact & PropsFromMaterialUI;
486+
interface NavBottomListItem extends TheTypeFromMaterialUI {
487+
value: string;
488+
}
489+
declare type UA = undefined;
490+
declare type UB = {
491+
x?: never;
492+
}['x'];
493+
declare type UC = UA & UB;

tests/baselines/reference/strictOptionalProperties1.symbols

+43
Original file line numberDiff line numberDiff line change
@@ -670,3 +670,46 @@ a = e; // Error
670670
>a : Symbol(a, Decl(strictOptionalProperties1.ts, 201, 11))
671671
>e : Symbol(e, Decl(strictOptionalProperties1.ts, 189, 13))
672672

673+
// Repro from #46004
674+
675+
interface PropsFromReact {
676+
>PropsFromReact : Symbol(PropsFromReact, Decl(strictOptionalProperties1.ts, 210, 6))
677+
678+
onClick?: () => void;
679+
>onClick : Symbol(PropsFromReact.onClick, Decl(strictOptionalProperties1.ts, 214, 26))
680+
}
681+
682+
interface PropsFromMaterialUI {
683+
>PropsFromMaterialUI : Symbol(PropsFromMaterialUI, Decl(strictOptionalProperties1.ts, 216, 1))
684+
685+
onClick?: (() => void) | undefined;
686+
>onClick : Symbol(PropsFromMaterialUI.onClick, Decl(strictOptionalProperties1.ts, 218, 31))
687+
}
688+
689+
type TheTypeFromMaterialUI = PropsFromReact & PropsFromMaterialUI;
690+
>TheTypeFromMaterialUI : Symbol(TheTypeFromMaterialUI, Decl(strictOptionalProperties1.ts, 220, 1))
691+
>PropsFromReact : Symbol(PropsFromReact, Decl(strictOptionalProperties1.ts, 210, 6))
692+
>PropsFromMaterialUI : Symbol(PropsFromMaterialUI, Decl(strictOptionalProperties1.ts, 216, 1))
693+
694+
interface NavBottomListItem extends TheTypeFromMaterialUI {
695+
>NavBottomListItem : Symbol(NavBottomListItem, Decl(strictOptionalProperties1.ts, 222, 66))
696+
>TheTypeFromMaterialUI : Symbol(TheTypeFromMaterialUI, Decl(strictOptionalProperties1.ts, 220, 1))
697+
698+
value: string;
699+
>value : Symbol(NavBottomListItem.value, Decl(strictOptionalProperties1.ts, 224, 59))
700+
}
701+
702+
// Repro from #46004
703+
704+
type UA = undefined; // Explicit undefined type
705+
>UA : Symbol(UA, Decl(strictOptionalProperties1.ts, 226, 1))
706+
707+
type UB = { x?: never }['x']; // undefined from missing property
708+
>UB : Symbol(UB, Decl(strictOptionalProperties1.ts, 230, 20))
709+
>x : Symbol(x, Decl(strictOptionalProperties1.ts, 231, 11))
710+
711+
type UC = UA & UB; // undefined
712+
>UC : Symbol(UC, Decl(strictOptionalProperties1.ts, 231, 29))
713+
>UA : Symbol(UA, Decl(strictOptionalProperties1.ts, 226, 1))
714+
>UB : Symbol(UB, Decl(strictOptionalProperties1.ts, 230, 20))
715+

tests/baselines/reference/strictOptionalProperties1.types

+32
Original file line numberDiff line numberDiff line change
@@ -780,3 +780,35 @@ a = e; // Error
780780
>a : { [x: string]: string | number; }
781781
>e : string | boolean | undefined
782782

783+
// Repro from #46004
784+
785+
interface PropsFromReact {
786+
onClick?: () => void;
787+
>onClick : (() => void) | undefined
788+
}
789+
790+
interface PropsFromMaterialUI {
791+
onClick?: (() => void) | undefined;
792+
>onClick : (() => void) | undefined
793+
}
794+
795+
type TheTypeFromMaterialUI = PropsFromReact & PropsFromMaterialUI;
796+
>TheTypeFromMaterialUI : TheTypeFromMaterialUI
797+
798+
interface NavBottomListItem extends TheTypeFromMaterialUI {
799+
value: string;
800+
>value : string
801+
}
802+
803+
// Repro from #46004
804+
805+
type UA = undefined; // Explicit undefined type
806+
>UA : undefined
807+
808+
type UB = { x?: never }['x']; // undefined from missing property
809+
>UB : undefined
810+
>x : undefined
811+
812+
type UC = UA & UB; // undefined
813+
>UC : undefined
814+

tests/cases/compiler/strictOptionalProperties1.ts

+23
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,26 @@ a = b;
213213
a = c;
214214
a = d; // Error
215215
a = e; // Error
216+
217+
// Repro from #46004
218+
219+
interface PropsFromReact {
220+
onClick?: () => void;
221+
}
222+
223+
interface PropsFromMaterialUI {
224+
onClick?: (() => void) | undefined;
225+
}
226+
227+
type TheTypeFromMaterialUI = PropsFromReact & PropsFromMaterialUI;
228+
229+
interface NavBottomListItem extends TheTypeFromMaterialUI {
230+
value: string;
231+
}
232+
233+
// Repro from #46004
234+
235+
type UA = undefined; // Explicit undefined type
236+
type UB = { x?: never }['x']; // undefined from missing property
237+
238+
type UC = UA & UB; // undefined

0 commit comments

Comments
 (0)