From ca923664808ba5fb09e9c590e3fce31c876bf4b0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 9 Apr 2023 09:30:58 -0700 Subject: [PATCH 1/5] Revise discriminateTypeByDiscriminableItems function --- src/compiler/checker.ts | 61 ++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4b32f7459cfef..8362ad00c29e1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22607,41 +22607,39 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { - return findMatchingDiscriminantType(source, target, isRelatedTo, /*skipPartial*/ true) || + return findMatchingDiscriminantType(source, target, isRelatedTo) || findMatchingTypeReferenceOrTypeAliasReference(source, target) || findBestTypeForObjectLiteral(source, target) || findBestTypeForInvokable(source, target) || findMostOverlappyType(source, target); } - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: undefined, skipPartial?: boolean): Type | undefined; - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type, skipPartial?: boolean): Type; - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type, skipPartial?: boolean) { - // undefined=unknown, true=discriminated, false=not discriminated - // The state of each type progresses from left to right. Discriminated types stop at 'true'. - const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary) { + const types = target.types; + const include = types.map(t => !(t.flags & TypeFlags.Primitive)); for (const [getDiscriminatingType, propertyName] of discriminators) { - const targetProp = getUnionOrIntersectionProperty(target, propertyName); - if (skipPartial && targetProp && getCheckFlags(targetProp) & CheckFlags.ReadPartial) { - continue; - } - let i = 0; - for (const type of target.types) { - const targetType = getTypeOfPropertyOfType(type, propertyName); - if (targetType && related(getDiscriminatingType(), targetType)) { - discriminable[i] = discriminable[i] === undefined ? true : discriminable[i]; - } - else { - discriminable[i] = false; + // If the remaining target types include at least one with a matching discriminant, eliminate those that + // have non-matching discriminants. This ensures that we ignore erroneous discriminators and gradually + // refine the target set without eliminating every constituent (which would lead to `never`). + let exclude: number[] | undefined; + let matched = false; + for (let i = 0; i < types.length; i++) { + if (include[i]) { + const targetType = getTypeOfPropertyOfType(types[i], propertyName); + if (targetType && related(getDiscriminatingType(), targetType)) { + matched = true; + } + else { + (exclude ??= []).push(i); + } } - i++; + } + if (matched && exclude) { + for (const i of exclude) include[i] = false; } } - const match = discriminable.indexOf(/*searchElement*/ true); - if (match === -1) { - return defaultValue; - } - return getUnionType(target.types.filter((_, index) => discriminable[index])); + const filtered = contains(include, false) ? getUnionType(types.filter((_, i) => include[i])) : target; + return filtered.flags & TypeFlags.Never ? target : filtered; } /** @@ -29276,8 +29274,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { s => [() => undefinedType, s.escapedName] as [() => Type, __String] ) ), - isTypeAssignableTo, - contextualType + isTypeAssignableTo ); } @@ -29293,8 +29290,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { s => [() => undefinedType, s.escapedName] as [() => Type, __String] ) ), - isTypeAssignableTo, - contextualType + isTypeAssignableTo ); } @@ -48739,7 +48735,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly - function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary, skipPartial?: boolean) { + function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary) { if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { const match = getMatchingUnionConstituentForType(target as UnionType, source); if (match) { @@ -48749,7 +48745,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (sourceProperties) { const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); if (sourcePropertiesFiltered) { - return discriminateTypeByDiscriminableItems(target as UnionType, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo, /*defaultValue*/ undefined, skipPartial); + const discriminated = discriminateTypeByDiscriminableItems(target as UnionType, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo); + if (discriminated !== target) { + return discriminated; + } } } } From cf47676f1cf8bf2c878020e99396d477cbc41166 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 9 Apr 2023 09:31:07 -0700 Subject: [PATCH 2/5] Accept new baselines --- ...entCompatWithDiscriminatedUnion.errors.txt | 32 ++++++++----------- ...yCheckWithMultipleDiscriminants.errors.txt | 16 ++++++---- .../jsxComponentTypeErrors.errors.txt | 6 ++-- .../objectLiteralNormalization.errors.txt | 10 +++--- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt index 1479d824429a1..130b836d052a1 100644 --- a/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt +++ b/tests/baselines/reference/assignmentCompatWithDiscriminatedUnion.errors.txt @@ -1,15 +1,13 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts(44,5): error TS2322: Type 'S' is not assignable to type 'T'. - Type 'S' is not assignable to type '{ a: 2; b: 3; }'. - Types of property 'a' are incompatible. - Type '0 | 2' is not assignable to type '2'. - Type '0' is not assignable to type '2'. + Type 'S' is not assignable to type '{ a: 0; b: 4 | 1; } | { a: 1; b: 2 | 4; }'. + Type 'S' is not assignable to type '{ a: 1; b: 2 | 4; }'. + Types of property 'a' are incompatible. + Type '0 | 2' is not assignable to type '1'. + Type '0' is not assignable to type '1'. tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts(58,5): error TS2322: Type 'S' is not assignable to type 'T'. - Property 'c' is missing in type 'S' but required in type '{ a: 2; b: 4 | 3; c: string; }'. + Type 'S' is not assignable to type '{ a: 0; b: 4 | 1; } | { a: 2; b: 4 | 3; c: string; }'. + Property 'c' is missing in type 'S' but required in type '{ a: 2; b: 4 | 3; c: string; }'. tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts(82,5): error TS2322: Type 'S' is not assignable to type 'T'. - Type 'S' is not assignable to type '{ a: N; b: N; c: 2; }'. - Types of property 'c' are incompatible. - Type 'N' is not assignable to type '2'. - Type '0' is not assignable to type '2'. ==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts (3 errors) ==== @@ -59,10 +57,11 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme t = s; ~ !!! error TS2322: Type 'S' is not assignable to type 'T'. -!!! error TS2322: Type 'S' is not assignable to type '{ a: 2; b: 3; }'. -!!! error TS2322: Types of property 'a' are incompatible. -!!! error TS2322: Type '0 | 2' is not assignable to type '2'. -!!! error TS2322: Type '0' is not assignable to type '2'. +!!! error TS2322: Type 'S' is not assignable to type '{ a: 0; b: 4 | 1; } | { a: 1; b: 2 | 4; }'. +!!! error TS2322: Type 'S' is not assignable to type '{ a: 1; b: 2 | 4; }'. +!!! error TS2322: Types of property 'a' are incompatible. +!!! error TS2322: Type '0 | 2' is not assignable to type '1'. +!!! error TS2322: Type '0' is not assignable to type '1'. } // Unmatched non-discriminants @@ -79,7 +78,8 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme t = s; ~ !!! error TS2322: Type 'S' is not assignable to type 'T'. -!!! error TS2322: Property 'c' is missing in type 'S' but required in type '{ a: 2; b: 4 | 3; c: string; }'. +!!! error TS2322: Type 'S' is not assignable to type '{ a: 0; b: 4 | 1; } | { a: 2; b: 4 | 3; c: string; }'. +!!! error TS2322: Property 'c' is missing in type 'S' but required in type '{ a: 2; b: 4 | 3; c: string; }'. !!! related TS2728 tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts:52:36: 'c' is declared here. } @@ -107,10 +107,6 @@ tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignme t = s; ~ !!! error TS2322: Type 'S' is not assignable to type 'T'. -!!! error TS2322: Type 'S' is not assignable to type '{ a: N; b: N; c: 2; }'. -!!! error TS2322: Types of property 'c' are incompatible. -!!! error TS2322: Type 'N' is not assignable to type '2'. -!!! error TS2322: Type '0' is not assignable to type '2'. } // https://github.com/Microsoft/TypeScript/issues/14865 diff --git a/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt b/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt index 62f17936146a4..078c2256851c9 100644 --- a/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt +++ b/tests/baselines/reference/excessPropertyCheckWithMultipleDiscriminants.errors.txt @@ -10,8 +10,10 @@ tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(83,5): erro Object literal may only specify known properties, and 'b' does not exist in type 'Common | (Common & A)'. tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(93,5): error TS2322: Type '{ type: "A"; n: number; a: number; b: number; }' is not assignable to type 'CommonWithDisjointOverlappingOptionals'. Object literal may only specify known properties, and 'b' does not exist in type 'Common | A'. -tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(130,5): error TS2322: Type '"string"' is not assignable to type '"number"'. -tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(136,5): error TS2322: Type '"string"' is not assignable to type '"number"'. +tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(131,5): error TS2322: Type '{ type: "string"; autoIncrement: boolean; required: true; }' is not assignable to type 'Attribute'. + Object literal may only specify known properties, and 'autoIncrement' does not exist in type 'StringAttribute | OneToOneAttribute'. +tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(137,5): error TS2322: Type '{ type: "string"; autoIncrement: boolean; required: true; }' is not assignable to type 'Attribute2'. + Object literal may only specify known properties, and 'autoIncrement' does not exist in type 'StringAttribute'. ==== tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts (8 errors) ==== @@ -163,17 +165,19 @@ tests/cases/compiler/excessPropertyCheckWithMultipleDiscriminants.ts(136,5): err // both should error due to excess properties const attributes: Attribute = { type: 'string', - ~~~~ -!!! error TS2322: Type '"string"' is not assignable to type '"number"'. autoIncrement: true, + ~~~~~~~~~~~~~ +!!! error TS2322: Type '{ type: "string"; autoIncrement: boolean; required: true; }' is not assignable to type 'Attribute'. +!!! error TS2322: Object literal may only specify known properties, and 'autoIncrement' does not exist in type 'StringAttribute | OneToOneAttribute'. required: true, }; const attributes2: Attribute2 = { type: 'string', - ~~~~ -!!! error TS2322: Type '"string"' is not assignable to type '"number"'. autoIncrement: true, + ~~~~~~~~~~~~~ +!!! error TS2322: Type '{ type: "string"; autoIncrement: boolean; required: true; }' is not assignable to type 'Attribute2'. +!!! error TS2322: Object literal may only specify known properties, and 'autoIncrement' does not exist in type 'StringAttribute'. required: true, }; \ No newline at end of file diff --git a/tests/baselines/reference/jsxComponentTypeErrors.errors.txt b/tests/baselines/reference/jsxComponentTypeErrors.errors.txt index 9f25590c593ad..5f89f6edfa998 100644 --- a/tests/baselines/reference/jsxComponentTypeErrors.errors.txt +++ b/tests/baselines/reference/jsxComponentTypeErrors.errors.txt @@ -17,7 +17,8 @@ tests/cases/compiler/jsxComponentTypeErrors.tsx(27,16): error TS2786: 'ClassComp tests/cases/compiler/jsxComponentTypeErrors.tsx(28,16): error TS2786: 'MixedComponent' cannot be used as a JSX component. Its element type 'ClassComponent | { type: string | undefined; }' is not a valid JSX element. Type 'ClassComponent' is not assignable to type 'Element | ElementClass | null'. - Type 'ClassComponent' is not assignable to type 'ElementClass'. + Type 'ClassComponent' is not assignable to type 'Element | ElementClass'. + Type 'ClassComponent' is not assignable to type 'ElementClass'. tests/cases/compiler/jsxComponentTypeErrors.tsx(37,16): error TS2786: 'obj.MemberFunctionComponent' cannot be used as a JSX component. Its return type '{}' is not a valid JSX element. Property 'type' is missing in type '{}' but required in type 'Element'. @@ -79,7 +80,8 @@ tests/cases/compiler/jsxComponentTypeErrors.tsx(38,16): error TS2786: 'obj. Memb !!! error TS2786: 'MixedComponent' cannot be used as a JSX component. !!! error TS2786: Its element type 'ClassComponent | { type: string | undefined; }' is not a valid JSX element. !!! error TS2786: Type 'ClassComponent' is not assignable to type 'Element | ElementClass | null'. -!!! error TS2786: Type 'ClassComponent' is not assignable to type 'ElementClass'. +!!! error TS2786: Type 'ClassComponent' is not assignable to type 'Element | ElementClass'. +!!! error TS2786: Type 'ClassComponent' is not assignable to type 'ElementClass'. const obj = { MemberFunctionComponent() { diff --git a/tests/baselines/reference/objectLiteralNormalization.errors.txt b/tests/baselines/reference/objectLiteralNormalization.errors.txt index a6ffc718f913c..25c20b5c23d31 100644 --- a/tests/baselines/reference/objectLiteralNormalization.errors.txt +++ b/tests/baselines/reference/objectLiteralNormalization.errors.txt @@ -5,9 +5,8 @@ tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts(9,1): error TS2322: Type '{ c: true; }' is not assignable to type '{ a: number; b?: undefined; c?: undefined; } | { a: number; b: string; c?: undefined; } | { a: number; b: string; c: boolean; }'. Type '{ c: true; }' is missing the following properties from type '{ a: number; b: string; c: boolean; }': a, b tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts(17,1): error TS2322: Type '{ a: string; b: number; }' is not assignable to type '{ a: number; b: number; } | { a: string; b?: undefined; } | { a?: undefined; b?: undefined; }'. - Type '{ a: string; b: number; }' is not assignable to type '{ a?: undefined; b?: undefined; }'. - Types of property 'a' are incompatible. - Type 'string' is not assignable to type 'undefined'. + Types of property 'b' are incompatible. + Type 'number' is not assignable to type 'undefined'. tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts(18,1): error TS2322: Type '{ a: number; }' is not assignable to type '{ a: number; b: number; } | { a: string; b?: undefined; } | { a?: undefined; b?: undefined; }'. Property 'b' is missing in type '{ a: number; }' but required in type '{ a: number; b: number; }'. tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts(48,20): error TS2322: Type '2' is not assignable to type '1'. @@ -44,9 +43,8 @@ tests/cases/conformance/expressions/objectLiterals/objectLiteralNormalization.ts a2 = { a: "def", b: 20 }; // Error ~~ !!! error TS2322: Type '{ a: string; b: number; }' is not assignable to type '{ a: number; b: number; } | { a: string; b?: undefined; } | { a?: undefined; b?: undefined; }'. -!!! error TS2322: Type '{ a: string; b: number; }' is not assignable to type '{ a?: undefined; b?: undefined; }'. -!!! error TS2322: Types of property 'a' are incompatible. -!!! error TS2322: Type 'string' is not assignable to type 'undefined'. +!!! error TS2322: Types of property 'b' are incompatible. +!!! error TS2322: Type 'number' is not assignable to type 'undefined'. a2 = { a: 1 }; // Error ~~ !!! error TS2322: Type '{ a: number; }' is not assignable to type '{ a: number; b: number; } | { a: string; b?: undefined; } | { a?: undefined; b?: undefined; }'. From 5c2b5559b33ce347e280efb3d81ed848cd0dc5f6 Mon Sep 17 00:00:00 2001 From: Maria Solano Date: Sun, 9 Apr 2023 10:47:54 -0700 Subject: [PATCH 3/5] Add test from original issue --- .../fourslash/completionsUnionStringLiteralProperty.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/cases/fourslash/completionsUnionStringLiteralProperty.ts diff --git a/tests/cases/fourslash/completionsUnionStringLiteralProperty.ts b/tests/cases/fourslash/completionsUnionStringLiteralProperty.ts new file mode 100644 index 0000000000000..a3439d10d7efa --- /dev/null +++ b/tests/cases/fourslash/completionsUnionStringLiteralProperty.ts @@ -0,0 +1,9 @@ +/// + +//// type Foo = { a: 0, b: 'x' } | { a: 0, b: 'y' } | { a: 1, b: 'z' }; +//// const foo: Foo = { a: 0, b: '/*1*/' } +//// +//// type Bar = { a: 0, b: 'fx' } | { a: 0, b: 'fy' } | { a: 1, b: 'fz' }; +//// const bar: Bar = { a: 0, b: 'f/*2*/' } +verify.completions({ marker: "1", triggerCharacter: "'", includes: [ "x", "y" ], excludes: [ "z" ] }); +verify.completions({ marker: "2", triggerCharacter: "'", includes: [ "fx", "fy" ], excludes: [ "fz" ] }); \ No newline at end of file From 65de9c270c3423060a32ed5327087dc1d26f7ba7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 10 Apr 2023 07:06:29 -0700 Subject: [PATCH 4/5] Add more tests --- .../completionsUnionStringLiteralProperty.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/cases/fourslash/completionsUnionStringLiteralProperty.ts b/tests/cases/fourslash/completionsUnionStringLiteralProperty.ts index a3439d10d7efa..7a8aaa6614c83 100644 --- a/tests/cases/fourslash/completionsUnionStringLiteralProperty.ts +++ b/tests/cases/fourslash/completionsUnionStringLiteralProperty.ts @@ -5,5 +5,15 @@ //// //// type Bar = { a: 0, b: 'fx' } | { a: 0, b: 'fy' } | { a: 1, b: 'fz' }; //// const bar: Bar = { a: 0, b: 'f/*2*/' } +//// +//// type Baz = { x: 0, y: 0, z: 'a' } | { x: 0, y: 1, z: 'b' } | { x: 1, y: 0, z: 'c' } | { x: 1, y: 1, z: 'd' }; +//// const baz1: Baz = { z: '/*3*/' }; +//// const baz2: Baz = { x: 0, z: '/*4*/' }; +//// const baz3: Baz = { x: 0, y: 1, z: '/*5*/' }; +//// const baz4: Baz = { x: 2, y: 1, z: '/*6*/' }; verify.completions({ marker: "1", triggerCharacter: "'", includes: [ "x", "y" ], excludes: [ "z" ] }); -verify.completions({ marker: "2", triggerCharacter: "'", includes: [ "fx", "fy" ], excludes: [ "fz" ] }); \ No newline at end of file +verify.completions({ marker: "2", triggerCharacter: "'", includes: [ "fx", "fy" ], excludes: [ "fz" ] }); +verify.completions({ marker: "3", triggerCharacter: "'", includes: [ "a", "b", "c", "d" ] }); +verify.completions({ marker: "4", triggerCharacter: "'", includes: [ "a", "b" ], excludes: [ "c", "d" ] }); +verify.completions({ marker: "5", triggerCharacter: "'", includes: [ "b" ], excludes: [ "a", "c", "d" ] }); +verify.completions({ marker: "6", triggerCharacter: "'", includes: [ "b", "d" ], excludes: [ "a", "c" ] }); From 30d7a511a1e4b7824f81dc04043a5ccc5a3aa450 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 11 Apr 2023 15:12:26 -0700 Subject: [PATCH 5/5] Address CR feedback --- src/compiler/checker.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8362ad00c29e1..134716eeb02fb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22616,12 +22616,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary) { const types = target.types; - const include = types.map(t => !(t.flags & TypeFlags.Primitive)); + const include: Ternary[] = types.map(t => t.flags & TypeFlags.Primitive ? Ternary.False : Ternary.True); for (const [getDiscriminatingType, propertyName] of discriminators) { // If the remaining target types include at least one with a matching discriminant, eliminate those that // have non-matching discriminants. This ensures that we ignore erroneous discriminators and gradually // refine the target set without eliminating every constituent (which would lead to `never`). - let exclude: number[] | undefined; let matched = false; for (let i = 0; i < types.length; i++) { if (include[i]) { @@ -22630,15 +22629,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { matched = true; } else { - (exclude ??= []).push(i); + include[i] = Ternary.Maybe; } } } - if (matched && exclude) { - for (const i of exclude) include[i] = false; + // Turn each Ternary.Maybe into Ternary.False if there was a match. Otherwise, revert to Ternary.True. + for (let i = 0; i < types.length; i++) { + if (include[i] === Ternary.Maybe) { + include[i] = matched ? Ternary.False : Ternary.True; + } } } - const filtered = contains(include, false) ? getUnionType(types.filter((_, i) => include[i])) : target; + const filtered = contains(include, Ternary.False) ? getUnionType(types.filter((_, i) => include[i])) : target; return filtered.flags & TypeFlags.Never ? target : filtered; }