Skip to content

Commit 88adf80

Browse files
authored
Make special intersections order-independent (#52782)
1 parent 3f4d16a commit 88adf80

File tree

8 files changed

+25
-9
lines changed

8 files changed

+25
-9
lines changed

src/compiler/checker.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -16766,12 +16766,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1676616766
return reduceLeft(types, (n, t) => n + getConstituentCount(t), 0);
1676716767
}
1676816768

16769+
function areIntersectedTypesAvoidingPrimitiveReduction(t1: Type, t2: Type) {
16770+
return !!(t1.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) && t2 === emptyTypeLiteralType;
16771+
}
16772+
1676916773
function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type {
1677016774
const links = getNodeLinks(node);
1677116775
if (!links.resolvedType) {
1677216776
const aliasSymbol = getAliasSymbolForTypeNode(node);
1677316777
const types = map(node.types, getTypeFromTypeNode);
16774-
const noSupertypeReduction = types.length === 2 && !!(types[0].flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) && types[1] === emptyTypeLiteralType;
16778+
const noSupertypeReduction = types.length === 2 && (areIntersectedTypesAvoidingPrimitiveReduction(types[0], types[1]) || areIntersectedTypesAvoidingPrimitiveReduction(types[1], types[0]));
1677516779
links.resolvedType = getIntersectionType(types, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol), noSupertypeReduction);
1677616780
}
1677716781
return links.resolvedType;

src/services/utilities.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -2165,15 +2165,19 @@ export function isStringOrRegularExpressionOrTemplateLiteral(kind: SyntaxKind):
21652165
return false;
21662166
}
21672167

2168+
function areIntersectedTypesAvoidingStringReduction(checker: TypeChecker, t1: Type, t2: Type) {
2169+
return !!(t1.flags & TypeFlags.String) && checker.isEmptyAnonymousObjectType(t2);
2170+
}
2171+
21682172
/** @internal */
21692173
export function isStringAndEmptyAnonymousObjectIntersection(type: Type) {
21702174
if (!type.isIntersection()) {
21712175
return false;
21722176
}
21732177

21742178
const { types, checker } = type;
2175-
return types.length === 2
2176-
&& (types[0].flags & TypeFlags.String) && checker.isEmptyAnonymousObjectType(types[1]);
2179+
return types.length === 2 &&
2180+
(areIntersectedTypesAvoidingStringReduction(checker, types[0], types[1]) || areIntersectedTypesAvoidingStringReduction(checker, types[1], types[0]));
21772181
}
21782182

21792183
/** @internal */

tests/baselines/reference/unknownControlFlow.errors.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ tests/cases/conformance/types/unknown/unknownControlFlow.ts(293,5): error TS2345
66

77

88
==== tests/cases/conformance/types/unknown/unknownControlFlow.ts (5 errors) ====
9-
type T01 = {} & string; // string
9+
type T01 = {} & string; // {} & string
1010
type T02 = {} & 'a'; // 'a'
1111
type T03 = {} & object; // object
1212
type T04 = {} & { x: number }; // { x: number }

tests/baselines/reference/unknownControlFlow.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//// [unknownControlFlow.ts]
2-
type T01 = {} & string; // string
2+
type T01 = {} & string; // {} & string
33
type T02 = {} & 'a'; // 'a'
44
type T03 = {} & object; // object
55
type T04 = {} & { x: number }; // { x: number }

tests/baselines/reference/unknownControlFlow.symbols

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
=== tests/cases/conformance/types/unknown/unknownControlFlow.ts ===
2-
type T01 = {} & string; // string
2+
type T01 = {} & string; // {} & string
33
>T01 : Symbol(T01, Decl(unknownControlFlow.ts, 0, 0))
44

55
type T02 = {} & 'a'; // 'a'

tests/baselines/reference/unknownControlFlow.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
=== tests/cases/conformance/types/unknown/unknownControlFlow.ts ===
2-
type T01 = {} & string; // string
3-
>T01 : string
2+
type T01 = {} & string; // {} & string
3+
>T01 : {} & string
44

55
type T02 = {} & 'a'; // 'a'
66
>T02 : "a"

tests/cases/conformance/types/unknown/unknownControlFlow.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @strict: true
22
// @declaration: true
33

4-
type T01 = {} & string; // string
4+
type T01 = {} & string; // {} & string
55
type T02 = {} & 'a'; // 'a'
66
type T03 = {} & object; // object
77
type T04 = {} & { x: number }; // { x: number }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// <reference path="fourslash.ts" />
2+
//
3+
//// declare function a(arg: 'test' | (string & {})): void
4+
//// a('/*1*/')
5+
//// declare function b(arg: 'test' | ({} & string)): void
6+
//// b('/*2*/')
7+
8+
verify.completions({ marker: ["1", "2"], exact: ["test"] });

0 commit comments

Comments
 (0)