Skip to content

Commit 7372208

Browse files
author
Andy Hanson
committed
Fix bugs for unions of type predicates
1 parent 7001e71 commit 7372208

File tree

6 files changed

+225
-29
lines changed

6 files changed

+225
-29
lines changed

src/compiler/checker.ts

Lines changed: 92 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -298,12 +298,12 @@ namespace ts {
298298
markerSubType.constraint = markerSuperType;
299299
const markerOtherType = <TypeParameter>createType(TypeFlags.TypeParameter);
300300

301-
const noTypePredicate: IdentifierTypePredicate = { kind: TypePredicateKind.Identifier, parameterName: "<<unresolved>>", parameterIndex: 0, type: anyType };
301+
const noTypePredicate = createIdentifierTypePredicate("<<unresolved>>", 0, anyType);
302302

303-
const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
304-
const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
305-
const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
306-
const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
303+
const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, noTypePredicate, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
304+
const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, noTypePredicate, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
305+
const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, noTypePredicate, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
306+
const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, noTypePredicate, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
307307

308308
const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true);
309309
const jsObjectLiteralIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
@@ -5490,8 +5490,17 @@ namespace ts {
54905490
resolveObjectTypeMembers(type, source, typeParameters, typeArguments);
54915491
}
54925492

5493-
function createSignature(declaration: SignatureDeclaration, typeParameters: TypeParameter[], thisParameter: Symbol | undefined, parameters: Symbol[],
5494-
resolvedReturnType: Type | undefined, resolvedTypePredicate: TypePredicate | undefined, minArgumentCount: number, hasRestParameter: boolean, hasLiteralTypes: boolean): Signature {
5493+
function createSignature(
5494+
declaration: SignatureDeclaration,
5495+
typeParameters: TypeParameter[],
5496+
thisParameter: Symbol | undefined,
5497+
parameters: Symbol[],
5498+
resolvedReturnType: Type | undefined,
5499+
resolvedTypePredicate: TypePredicate | undefined,
5500+
minArgumentCount: number,
5501+
hasRestParameter: boolean,
5502+
hasLiteralTypes: boolean,
5503+
): Signature {
54955504
const sig = new Signature(checker);
54965505
sig.declaration = declaration;
54975506
sig.typeParameters = typeParameters;
@@ -5506,8 +5515,8 @@ namespace ts {
55065515
}
55075516

55085517
function cloneSignature(sig: Signature): Signature {
5509-
return createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, sig.resolvedReturnType,
5510-
sig.resolvedTypePredicate, sig.minArgumentCount, sig.hasRestParameter, sig.hasLiteralTypes);
5518+
return createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, /*resolvedReturnType*/ undefined,
5519+
/*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.hasRestParameter, sig.hasLiteralTypes);
55115520
}
55125521

55135522
function getDefaultConstructSignatures(classType: InterfaceType): Signature[] {
@@ -5525,9 +5534,12 @@ namespace ts {
55255534
const minTypeArgumentCount = getMinTypeArgumentCount(baseSig.typeParameters);
55265535
const typeParamCount = length(baseSig.typeParameters);
55275536
if ((isJavaScript || typeArgCount >= minTypeArgumentCount) && typeArgCount <= typeParamCount) {
5528-
const sig = typeParamCount ? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, minTypeArgumentCount, isJavaScript)) : cloneSignature(baseSig);
5537+
const sig = typeParamCount
5538+
? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, minTypeArgumentCount, isJavaScript))
5539+
: cloneSignature(baseSig);
55295540
sig.typeParameters = classType.localTypeParameters;
55305541
sig.resolvedReturnType = classType;
5542+
sig.resolvedTypePredicate = noTypePredicate;
55315543
result.push(sig);
55325544
}
55335545
}
@@ -5589,8 +5601,6 @@ namespace ts {
55895601
const thisType = getUnionType(map(unionSignatures, sig => getTypeOfSymbol(sig.thisParameter) || anyType), /*subtypeReduction*/ true);
55905602
s.thisParameter = createSymbolWithType(signature.thisParameter, thisType);
55915603
}
5592-
// Clear resolved return type we possibly got from cloneSignature
5593-
s.resolvedReturnType = undefined;
55945604
s.unionSignatures = unionSignatures;
55955605
}
55965606
(result || (result = [])).push(s);
@@ -5674,6 +5684,7 @@ namespace ts {
56745684
signatures = map(signatures, s => {
56755685
const clone = cloneSignature(s);
56765686
clone.resolvedReturnType = includeMixinType(getReturnTypeOfSignature(s), types, i);
5687+
clone.resolvedTypePredicate = noTypePredicate; // TODO: GH#17757
56775688
return clone;
56785689
});
56795690
}
@@ -6103,14 +6114,13 @@ namespace ts {
61036114

61046115
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol {
61056116
let props: Symbol[];
6106-
const types = containingType.types;
61076117
const isUnion = containingType.flags & TypeFlags.Union;
61086118
const excludeModifiers = isUnion ? ModifierFlags.NonPublicAccessibilityModifier : 0;
61096119
// Flags we want to propagate to the result if they exist in all source symbols
61106120
let commonFlags = isUnion ? SymbolFlags.None : SymbolFlags.Optional;
61116121
let syntheticFlag = CheckFlags.SyntheticMethod;
61126122
let checkFlags = 0;
6113-
for (const current of types) {
6123+
for (const current of containingType.types) {
61146124
const type = getApparentType(current);
61156125
if (type !== unknownType) {
61166126
const prop = getPropertyOfType(type, name);
@@ -6344,22 +6354,26 @@ namespace ts {
63446354

63456355
function createTypePredicateFromTypePredicateNode(node: TypePredicateNode): IdentifierTypePredicate | ThisTypePredicate {
63466356
const { parameterName } = node;
6357+
const type = getTypeFromTypeNode(node.type);
63476358
if (parameterName.kind === SyntaxKind.Identifier) {
6348-
return {
6349-
kind: TypePredicateKind.Identifier,
6350-
parameterName: parameterName ? parameterName.escapedText : undefined,
6351-
parameterIndex: parameterName ? getTypePredicateParameterIndex((node.parent as SignatureDeclaration).parameters, parameterName) : undefined,
6352-
type: getTypeFromTypeNode(node.type)
6353-
} as IdentifierTypePredicate;
6359+
return createIdentifierTypePredicate(
6360+
parameterName && parameterName.escapedText as string, // TODO: GH#18217
6361+
parameterName && getTypePredicateParameterIndex((node.parent as SignatureDeclaration).parameters, parameterName),
6362+
type);
63546363
}
63556364
else {
6356-
return {
6357-
kind: TypePredicateKind.This,
6358-
type: getTypeFromTypeNode(node.type)
6359-
};
6365+
return createThisTypePredicate(type);
63606366
}
63616367
}
63626368

6369+
function createIdentifierTypePredicate(parameterName: string | undefined, parameterIndex: number | undefined, type: Type): IdentifierTypePredicate {
6370+
return { kind: TypePredicateKind.Identifier, parameterName, parameterIndex, type };
6371+
}
6372+
6373+
function createThisTypePredicate(type: Type): ThisTypePredicate {
6374+
return { kind: TypePredicateKind.This, type };
6375+
}
6376+
63636377
/**
63646378
* Gets the minimum number of type arguments needed to satisfy all non-optional type
63656379
* parameters.
@@ -6604,8 +6618,14 @@ namespace ts {
66046618

66056619
function getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined {
66066620
if (!signature.resolvedTypePredicate) {
6607-
const targetTypePredicate = getTypePredicateOfSignature(signature.target);
6608-
signature.resolvedTypePredicate = targetTypePredicate ? instantiateTypePredicate(targetTypePredicate, signature.mapper) : noTypePredicate;
6621+
if (signature.target) {
6622+
const targetTypePredicate = getTypePredicateOfSignature(signature.target);
6623+
signature.resolvedTypePredicate = targetTypePredicate ? instantiateTypePredicate(targetTypePredicate, signature.mapper) : noTypePredicate;
6624+
}
6625+
else {
6626+
Debug.assert(!!signature.unionSignatures);
6627+
signature.resolvedTypePredicate = getUnionTypePredicate(signature.unionSignatures);
6628+
}
66096629
}
66106630
return signature.resolvedTypePredicate === noTypePredicate ? undefined : signature.resolvedTypePredicate;
66116631
}
@@ -7480,6 +7500,42 @@ namespace ts {
74807500
return getUnionTypeFromSortedList(typeSet, aliasSymbol, aliasTypeArguments);
74817501
}
74827502

7503+
function getUnionTypePredicate(signatures: ReadonlyArray<Signature>): TypePredicate | undefined {
7504+
let first: TypePredicate | undefined;
7505+
const types: Type[] = [];
7506+
for (let i = 0; i < signatures.length; i++) {
7507+
const pred = getTypePredicateOfSignature(signatures[i]);
7508+
if (!pred) {
7509+
continue;
7510+
}
7511+
7512+
if (first) {
7513+
if (!typePredicateKindsMatch(first, pred)) {
7514+
// No common type predicate.
7515+
return undefined;
7516+
}
7517+
}
7518+
else {
7519+
first = pred;
7520+
}
7521+
types.push(pred.type);
7522+
}
7523+
if (!first) {
7524+
// No union signatures had a type predicate.
7525+
return undefined;
7526+
}
7527+
const unionType = getUnionType(types);
7528+
return isIdentifierTypePredicate(first)
7529+
? createIdentifierTypePredicate(first.parameterName, first.parameterIndex, unionType)
7530+
: createThisTypePredicate(unionType);
7531+
}
7532+
7533+
function typePredicateKindsMatch(a: TypePredicate, b: TypePredicate): boolean {
7534+
return isIdentifierTypePredicate(a)
7535+
? isIdentifierTypePredicate(b) && a.parameterIndex === b.parameterIndex
7536+
: !isIdentifierTypePredicate(b);
7537+
}
7538+
74837539
// This function assumes the constituent type list is sorted and deduplicated.
74847540
function getUnionTypeFromSortedList(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
74857541
if (types.length === 0) {
@@ -10167,11 +10223,20 @@ namespace ts {
1016710223
result &= related;
1016810224
}
1016910225
if (!ignoreReturnTypes) {
10170-
result &= compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target));
10226+
const sourceTypePredicate = getTypePredicateOfSignature(source);
10227+
const targetTypePredicate = getTypePredicateOfSignature(target);
10228+
result &= sourceTypePredicate !== undefined || targetTypePredicate !== undefined
10229+
? compareTypePredicatesIdentical(sourceTypePredicate, targetTypePredicate, compareTypes)
10230+
// If they're both type predicates their return types will both be `boolean`, so no need to compare those.
10231+
: compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target));
1017110232
}
1017210233
return result;
1017310234
}
1017410235

10236+
function compareTypePredicatesIdentical(source: TypePredicate | undefined, target: TypePredicate | undefined, compareTypes: (s: Type, t: Type) => Ternary): Ternary {
10237+
return source === undefined || target === undefined || !typePredicateKindsMatch(source, target) ? Ternary.False : compareTypes(source.type, target.type);
10238+
}
10239+
1017510240
function isRestParameterIndex(signature: Signature, parameterIndex: number) {
1017610241
return signature.hasRestParameter && parameterIndex >= signature.parameters.length - 1;
1017710242
}
@@ -13612,8 +13677,6 @@ namespace ts {
1361213677
let result: Signature;
1361313678
if (signatureList) {
1361413679
result = cloneSignature(signatureList[0]);
13615-
// Clear resolved return type we possibly got from cloneSignature
13616-
result.resolvedReturnType = undefined;
1361713680
result.unionSignatures = signatureList;
1361813681
}
1361913682
return result;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//// [typePredicatesInUnion.ts]
2+
interface A {
3+
pred(x: {}): x is boolean;
4+
}
5+
interface B {
6+
pred(x: {}): x is string;
7+
}
8+
9+
type Or = A | B;
10+
11+
function f(o: Or, x: {}) {
12+
if (o.pred(x)) {
13+
x;
14+
}
15+
}
16+
17+
18+
//// [typePredicatesInUnion.js]
19+
function f(o, x) {
20+
if (o.pred(x)) {
21+
x;
22+
}
23+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
=== tests/cases/compiler/typePredicatesInUnion.ts ===
2+
interface A {
3+
>A : Symbol(A, Decl(typePredicatesInUnion.ts, 0, 0))
4+
5+
pred(x: {}): x is boolean;
6+
>pred : Symbol(A.pred, Decl(typePredicatesInUnion.ts, 0, 13))
7+
>x : Symbol(x, Decl(typePredicatesInUnion.ts, 1, 9))
8+
>x : Symbol(x, Decl(typePredicatesInUnion.ts, 1, 9))
9+
}
10+
interface B {
11+
>B : Symbol(B, Decl(typePredicatesInUnion.ts, 2, 1))
12+
13+
pred(x: {}): x is string;
14+
>pred : Symbol(B.pred, Decl(typePredicatesInUnion.ts, 3, 13))
15+
>x : Symbol(x, Decl(typePredicatesInUnion.ts, 4, 9))
16+
>x : Symbol(x, Decl(typePredicatesInUnion.ts, 4, 9))
17+
}
18+
19+
type Or = A | B;
20+
>Or : Symbol(Or, Decl(typePredicatesInUnion.ts, 5, 1))
21+
>A : Symbol(A, Decl(typePredicatesInUnion.ts, 0, 0))
22+
>B : Symbol(B, Decl(typePredicatesInUnion.ts, 2, 1))
23+
24+
function f(o: Or, x: {}) {
25+
>f : Symbol(f, Decl(typePredicatesInUnion.ts, 7, 16))
26+
>o : Symbol(o, Decl(typePredicatesInUnion.ts, 9, 11))
27+
>Or : Symbol(Or, Decl(typePredicatesInUnion.ts, 5, 1))
28+
>x : Symbol(x, Decl(typePredicatesInUnion.ts, 9, 17))
29+
30+
if (o.pred(x)) {
31+
>o.pred : Symbol(pred, Decl(typePredicatesInUnion.ts, 0, 13), Decl(typePredicatesInUnion.ts, 3, 13))
32+
>o : Symbol(o, Decl(typePredicatesInUnion.ts, 9, 11))
33+
>pred : Symbol(pred, Decl(typePredicatesInUnion.ts, 0, 13), Decl(typePredicatesInUnion.ts, 3, 13))
34+
>x : Symbol(x, Decl(typePredicatesInUnion.ts, 9, 17))
35+
36+
x;
37+
>x : Symbol(x, Decl(typePredicatesInUnion.ts, 9, 17))
38+
}
39+
}
40+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
=== tests/cases/compiler/typePredicatesInUnion.ts ===
2+
interface A {
3+
>A : A
4+
5+
pred(x: {}): x is boolean;
6+
>pred : (x: {}) => x is boolean
7+
>x : {}
8+
>x : any
9+
}
10+
interface B {
11+
>B : B
12+
13+
pred(x: {}): x is string;
14+
>pred : (x: {}) => x is string
15+
>x : {}
16+
>x : any
17+
}
18+
19+
type Or = A | B;
20+
>Or : Or
21+
>A : A
22+
>B : B
23+
24+
function f(o: Or, x: {}) {
25+
>f : (o: Or, x: {}) => void
26+
>o : Or
27+
>Or : Or
28+
>x : {}
29+
30+
if (o.pred(x)) {
31+
>o.pred(x) : boolean
32+
>o.pred : ((x: {}) => x is boolean) | ((x: {}) => x is string)
33+
>o : Or
34+
>pred : ((x: {}) => x is boolean) | ((x: {}) => x is string)
35+
>x : {}
36+
37+
x;
38+
>x : string | boolean
39+
}
40+
}
41+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
interface A {
2+
pred(x: {}): x is boolean;
3+
}
4+
interface B {
5+
pred(x: {}): x is string;
6+
}
7+
8+
type Or = A | B;
9+
10+
function f(o: Or, x: {}) {
11+
if (o.pred(x)) {
12+
x;
13+
}
14+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
interface A {
2+
pred(x: {}, y: {}): x is boolean;
3+
}
4+
interface B {
5+
pred(x: {}, y: {}): y is string;
6+
}
7+
8+
type Or = A | B;
9+
10+
function f(o: Or, x: {}, y: {}) {
11+
if (o.pred(x, y)) {
12+
x;
13+
y;
14+
}
15+
}

0 commit comments

Comments
 (0)