diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 552a64cf77cbb..eea791de41a7a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16649,12 +16649,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return reduceLeft(types, (n, t) => n + getConstituentCount(t), 0); } + function areIntersectedTypesAvoidingPrimitiveReduction(t1: Type, t2: Type) { + return !!(t1.flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) && t2 === emptyTypeLiteralType; + } + function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { const aliasSymbol = getAliasSymbolForTypeNode(node); const types = map(node.types, getTypeFromTypeNode); - const noSupertypeReduction = types.length === 2 && !!(types[0].flags & (TypeFlags.String | TypeFlags.Number | TypeFlags.BigInt)) && types[1] === emptyTypeLiteralType; + const noSupertypeReduction = types.length === 2 && (areIntersectedTypesAvoidingPrimitiveReduction(types[0], types[1]) || areIntersectedTypesAvoidingPrimitiveReduction(types[1], types[0])); links.resolvedType = getIntersectionType(types, aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol), noSupertypeReduction); } return links.resolvedType; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 901f6ed67616c..1010c558e51a6 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -2163,6 +2163,10 @@ export function isStringOrRegularExpressionOrTemplateLiteral(kind: SyntaxKind): return false; } +function areIntersectedTypesAvoidingStringReduction(checker: TypeChecker, t1: Type, t2: Type) { + return !!(t1.flags & TypeFlags.String) && checker.isEmptyAnonymousObjectType(t2); +} + /** @internal */ export function isStringAndEmptyAnonymousObjectIntersection(type: Type) { if (!type.isIntersection()) { @@ -2170,8 +2174,8 @@ export function isStringAndEmptyAnonymousObjectIntersection(type: Type) { } const { types, checker } = type; - return types.length === 2 - && (types[0].flags & TypeFlags.String) && checker.isEmptyAnonymousObjectType(types[1]); + return types.length === 2 && + (areIntersectedTypesAvoidingStringReduction(checker, types[0], types[1]) || areIntersectedTypesAvoidingStringReduction(checker, types[1], types[0])); } /** @internal */ diff --git a/tests/baselines/reference/unknownControlFlow.errors.txt b/tests/baselines/reference/unknownControlFlow.errors.txt index 6056f775acf0a..020a3bfb64571 100644 --- a/tests/baselines/reference/unknownControlFlow.errors.txt +++ b/tests/baselines/reference/unknownControlFlow.errors.txt @@ -6,7 +6,7 @@ tests/cases/conformance/types/unknown/unknownControlFlow.ts(293,5): error TS2345 ==== tests/cases/conformance/types/unknown/unknownControlFlow.ts (5 errors) ==== - type T01 = {} & string; // string + type T01 = {} & string; // {} & string type T02 = {} & 'a'; // 'a' type T03 = {} & object; // object type T04 = {} & { x: number }; // { x: number } diff --git a/tests/baselines/reference/unknownControlFlow.js b/tests/baselines/reference/unknownControlFlow.js index 68233905e13b9..7ea8546627b02 100644 --- a/tests/baselines/reference/unknownControlFlow.js +++ b/tests/baselines/reference/unknownControlFlow.js @@ -1,5 +1,5 @@ //// [unknownControlFlow.ts] -type T01 = {} & string; // string +type T01 = {} & string; // {} & string type T02 = {} & 'a'; // 'a' type T03 = {} & object; // object type T04 = {} & { x: number }; // { x: number } diff --git a/tests/baselines/reference/unknownControlFlow.symbols b/tests/baselines/reference/unknownControlFlow.symbols index 762505aba1907..e4212868ac0c0 100644 --- a/tests/baselines/reference/unknownControlFlow.symbols +++ b/tests/baselines/reference/unknownControlFlow.symbols @@ -1,5 +1,5 @@ === tests/cases/conformance/types/unknown/unknownControlFlow.ts === -type T01 = {} & string; // string +type T01 = {} & string; // {} & string >T01 : Symbol(T01, Decl(unknownControlFlow.ts, 0, 0)) type T02 = {} & 'a'; // 'a' diff --git a/tests/baselines/reference/unknownControlFlow.types b/tests/baselines/reference/unknownControlFlow.types index 24cbd3b162850..8e75fcc7f2127 100644 --- a/tests/baselines/reference/unknownControlFlow.types +++ b/tests/baselines/reference/unknownControlFlow.types @@ -1,6 +1,6 @@ === tests/cases/conformance/types/unknown/unknownControlFlow.ts === -type T01 = {} & string; // string ->T01 : string +type T01 = {} & string; // {} & string +>T01 : {} & string type T02 = {} & 'a'; // 'a' >T02 : "a" diff --git a/tests/cases/conformance/types/unknown/unknownControlFlow.ts b/tests/cases/conformance/types/unknown/unknownControlFlow.ts index 1f82ab4944da5..752c70f68939a 100644 --- a/tests/cases/conformance/types/unknown/unknownControlFlow.ts +++ b/tests/cases/conformance/types/unknown/unknownControlFlow.ts @@ -1,7 +1,7 @@ // @strict: true // @declaration: true -type T01 = {} & string; // string +type T01 = {} & string; // {} & string type T02 = {} & 'a'; // 'a' type T03 = {} & object; // object type T04 = {} & { x: number }; // { x: number } diff --git a/tests/cases/fourslash/specialIntersectionsOrderIndependent.ts b/tests/cases/fourslash/specialIntersectionsOrderIndependent.ts new file mode 100644 index 0000000000000..ad648a16bc408 --- /dev/null +++ b/tests/cases/fourslash/specialIntersectionsOrderIndependent.ts @@ -0,0 +1,8 @@ +/// +// +//// declare function a(arg: 'test' | (string & {})): void +//// a('/*1*/') +//// declare function b(arg: 'test' | ({} & string)): void +//// b('/*2*/') + +verify.completions({ marker: ["1", "2"], exact: ["test"] });