Skip to content

Commit ec2bdfd

Browse files
committed
Add 'T extends U' type operator
1 parent 063eed1 commit ec2bdfd

10 files changed

Lines changed: 213 additions & 71 deletions

File tree

src/compiler/binder.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3422,6 +3422,7 @@ namespace ts {
34223422
case SyntaxKind.TypeAliasDeclaration:
34233423
case SyntaxKind.ThisType:
34243424
case SyntaxKind.TypeOperator:
3425+
case SyntaxKind.BinaryType:
34253426
case SyntaxKind.IndexedAccessType:
34263427
case SyntaxKind.MappedType:
34273428
case SyntaxKind.LiteralType:

src/compiler/checker.ts

Lines changed: 109 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ namespace ts {
264264
const intersectionTypes = createMap<IntersectionType>();
265265
const literalTypes = createMap<LiteralType>();
266266
const indexedAccessTypes = createMap<IndexedAccessType>();
267+
const conditionalTypes = createMap<ConditionalType>();
268+
const extendsTypes = createMap<ExtendsType>();
267269
const evolvingArrayTypes: EvolvingArrayType[] = [];
268270
const undefinedProperties = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;
269271

@@ -2621,11 +2623,15 @@ namespace ts {
26212623
return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode);
26222624
}
26232625
if (type.flags & TypeFlags.Conditional) {
2624-
const checkTypeNode = typeToTypeNodeHelper((<ConditionalType>type).checkType, context);
2625-
const extendskTypeNode = typeToTypeNodeHelper((<ConditionalType>type).extendsType, context);
2626+
const conditionTypeNode = typeToTypeNodeHelper((<ConditionalType>type).conditionType, context);
26262627
const trueTypeNode = typeToTypeNodeHelper((<ConditionalType>type).trueType, context);
26272628
const falseTypeNode = typeToTypeNodeHelper((<ConditionalType>type).falseType, context);
2628-
return createConditionalTypeNode(checkTypeNode, extendskTypeNode, trueTypeNode, falseTypeNode);
2629+
return createConditionalTypeNode(conditionTypeNode, trueTypeNode, falseTypeNode);
2630+
}
2631+
if (type.flags & TypeFlags.Extends) {
2632+
const leftTypeNode = typeToTypeNodeHelper((<ExtendsType>type).checkType, context);
2633+
const rightTypeNode = typeToTypeNodeHelper((<ExtendsType>type).extendsType, context);
2634+
return createBinaryTypeNode(leftTypeNode, SyntaxKind.ExtendsKeyword, rightTypeNode);
26292635
}
26302636

26312637
Debug.fail("Should be unreachable.");
@@ -3396,11 +3402,7 @@ namespace ts {
33963402
writePunctuation(writer, SyntaxKind.CloseBracketToken);
33973403
}
33983404
else if (type.flags & TypeFlags.Conditional) {
3399-
writeType((<ConditionalType>type).checkType, TypeFormatFlags.InElementType);
3400-
writeSpace(writer);
3401-
writer.writeKeyword("extends");
3402-
writeSpace(writer);
3403-
writeType((<ConditionalType>type).extendsType, TypeFormatFlags.InElementType);
3405+
writeType((<ConditionalType>type).conditionType, TypeFormatFlags.InElementType);
34043406
writeSpace(writer);
34053407
writePunctuation(writer, SyntaxKind.QuestionToken);
34063408
writeSpace(writer);
@@ -3410,6 +3412,13 @@ namespace ts {
34103412
writeSpace(writer);
34113413
writeType((<ConditionalType>type).falseType, TypeFormatFlags.InElementType);
34123414
}
3415+
else if (type.flags & TypeFlags.Extends) {
3416+
writeType((<ExtendsType>type).checkType, TypeFormatFlags.InElementType);
3417+
writeSpace(writer);
3418+
writer.writeKeyword("extends");
3419+
writeSpace(writer);
3420+
writeType((<ExtendsType>type).extendsType, TypeFormatFlags.InElementType);
3421+
}
34133422
else {
34143423
// Should never get here
34153424
// { ... }
@@ -6385,6 +6394,9 @@ namespace ts {
63856394
const falseBaseType = getBaseConstraint((<ConditionalType>t).trueType);
63866395
return trueBaseType && falseBaseType ? getUnionType([trueBaseType, falseBaseType]) : undefined;
63876396
}
6397+
if (t.flags & TypeFlags.Extends) {
6398+
return booleanType;
6399+
}
63886400
if (isGenericMappedType(t)) {
63896401
return emptyObjectType;
63906402
}
@@ -8109,6 +8121,12 @@ namespace ts {
81098121
false;
81108122
}
81118123

8124+
function isGenericConditionType(type: Type): boolean {
8125+
return type.flags & (TypeFlags.TypeVariable | TypeFlags.Extends) ? true :
8126+
type.flags & TypeFlags.UnionOrIntersection ? forEach((<UnionOrIntersectionType>type).types, isGenericConditionType) :
8127+
false;
8128+
}
8129+
81128130
// Return true if the given type is a non-generic object type with a string index signature and no
81138131
// other members.
81148132
function isStringIndexOnlyType(type: Type) {
@@ -8216,33 +8234,73 @@ namespace ts {
82168234
return links.resolvedType;
82178235
}
82188236

8219-
function getConditionalType(checkType: Type, extendsType: Type, trueType: Type, falseType: Type, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
8220-
if (checkType.flags & TypeFlags.Union) {
8221-
return getUnionType(map((<UnionType>checkType).types, t => getConditionalType(t, extendsType, trueType, falseType)),
8222-
/*subtypeReduction*/ false, aliasSymbol, aliasTypeArguments);
8223-
}
8224-
if (isTypeAssignableTo(checkType, extendsType)) {
8225-
return trueType;
8226-
}
8227-
if (!isGenericObjectType(checkType) && !isGenericObjectType(extendsType)) {
8228-
return falseType;
8229-
}
8237+
function createConditionalType(conditionType: Type, whenTrueType: Type, whenFalseType: Type, aliasSymbol: Symbol, aliasTypeArguments: Type[]) {
82308238
const type = <ConditionalType>createType(TypeFlags.Conditional);
8231-
type.checkType = checkType;
8232-
type.extendsType = extendsType;
8233-
type.trueType = trueType;
8234-
type.falseType = falseType;
8239+
type.conditionType = conditionType;
8240+
type.trueType = whenTrueType;
8241+
type.falseType = whenFalseType;
82358242
type.aliasSymbol = aliasSymbol;
82368243
type.aliasTypeArguments = aliasTypeArguments;
82378244
return type;
82388245
}
82398246

8247+
function getConditionalType(condition: Type, whenTrue: Type, whenFalse: Type, aliasSymbol: Symbol, aliasTypeArguments: Type[], mapper: TypeMapper): Type {
8248+
if (!isGenericConditionType(condition)) {
8249+
return condition.flags & TypeFlags.Never ? neverType : getUnionType([
8250+
typeMaybeAssignableTo(condition, trueType) ? instantiateType(whenTrue, mapper) : neverType,
8251+
typeMaybeAssignableTo(condition, falseType) ? instantiateType(whenFalse, mapper) : neverType]);
8252+
}
8253+
const resultTrueType = instantiateType(whenTrue, mapper);
8254+
const resultFalseType = instantiateType(whenFalse, mapper);
8255+
const resultTypeArguments = instantiateTypes(aliasTypeArguments, mapper);
8256+
const id = condition.id + "," + resultTrueType.id + "," + resultFalseType.id;
8257+
let type = conditionalTypes.get(id);
8258+
if (!type) {
8259+
conditionalTypes.set(id, type = createConditionalType(condition, resultTrueType, resultFalseType, aliasSymbol, resultTypeArguments));
8260+
}
8261+
return type;
8262+
}
8263+
82408264
function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type {
82418265
const links = getNodeLinks(node);
82428266
if (!links.resolvedType) {
8243-
links.resolvedType = getConditionalType(getTypeFromTypeNode(node.checkType), getTypeFromTypeNode(node.extendsType),
8267+
links.resolvedType = getConditionalType(getTypeFromTypeNode(node.conditionType),
82448268
getTypeFromTypeNode(node.trueType), getTypeFromTypeNode(node.falseType),
8245-
getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node));
8269+
getAliasSymbolForTypeNode(node), getAliasTypeArgumentsForTypeNode(node), identityMapper);
8270+
}
8271+
return links.resolvedType;
8272+
}
8273+
8274+
function createExtendsType(checkType: Type, extendsType: Type) {
8275+
const type = <ExtendsType>createType(TypeFlags.Extends);
8276+
type.checkType = checkType;
8277+
type.extendsType = extendsType;
8278+
return type;
8279+
}
8280+
8281+
function getExtendsType(checkType: Type, extendsType: Type): Type {
8282+
// sys.write(`getExtendsType(${typeToString(checkType)}, ${typeToString(extendsType)})\n`);
8283+
if (checkType.flags & TypeFlags.Union) {
8284+
return getUnionType(map((<UnionType>checkType).types, t => getExtendsType(t, extendsType)), /*subtypeReduction*/ false);
8285+
}
8286+
if (checkType.flags & TypeFlags.Any) {
8287+
return booleanType;
8288+
}
8289+
if (!isGenericObjectType(checkType) && !isGenericObjectType(extendsType)) {
8290+
return isTypeAssignableTo(checkType, extendsType) ? trueType : falseType;
8291+
}
8292+
const id = checkType.id + "," + extendsType.id;
8293+
let type = extendsTypes.get(id);
8294+
if (!type) {
8295+
extendsTypes.set(id, type = createExtendsType(checkType, extendsType));
8296+
}
8297+
return type;
8298+
}
8299+
8300+
function getTypeFromBinaryTypeNode(node: BinaryTypeNode): Type {
8301+
const links = getNodeLinks(node);
8302+
if (!links.resolvedType) {
8303+
links.resolvedType = getExtendsType(getTypeFromTypeNode(node.left), getTypeFromTypeNode(node.right));
82468304
}
82478305
return links.resolvedType;
82488306
}
@@ -8541,6 +8599,8 @@ namespace ts {
85418599
return getTypeFromMappedTypeNode(<MappedTypeNode>node);
85428600
case SyntaxKind.ConditionalType:
85438601
return getTypeFromConditionalTypeNode(<ConditionalTypeNode>node);
8602+
case SyntaxKind.BinaryType:
8603+
return getTypeFromBinaryTypeNode(<BinaryTypeNode>node);
85448604
// This function assumes that an identifier or qualified name is a type expression
85458605
// Callers should first ensure this by calling isTypeNode
85468606
case SyntaxKind.Identifier:
@@ -8807,20 +8867,23 @@ namespace ts {
88078867
}
88088868

88098869
function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper): Type {
8810-
const checkType = type.checkType;
8811-
if (checkType.flags & TypeFlags.TypeParameter) {
8812-
const instantiatedType = mapper(<TypeParameter>checkType);
8813-
if (checkType !== instantiatedType && instantiatedType.flags & TypeFlags.Union) {
8814-
return mapType(instantiatedType, t => instantiateConditionalType(type, createReplacementMapper(checkType, t, mapper)));
8870+
const conditionType = type.conditionType;
8871+
if (conditionType.flags & TypeFlags.Extends) {
8872+
const checkType = (<ExtendsType>conditionType).checkType;
8873+
if (checkType.flags & TypeFlags.TypeParameter) {
8874+
const instantiatedType = mapper(<TypeParameter>checkType);
8875+
if (checkType !== instantiatedType && instantiatedType.flags & TypeFlags.Union) {
8876+
return mapType(instantiatedType, t => instantiateConditionalType(type, createReplacementMapper(checkType, t, mapper)));
8877+
}
88158878
}
88168879
}
88178880
return instantiateConditionalType(type, mapper);
88188881
}
88198882

88208883
function instantiateConditionalType(type: ConditionalType, mapper: TypeMapper): Type {
8821-
return getConditionalType(instantiateType((<ConditionalType>type).checkType, mapper), instantiateType((<ConditionalType>type).extendsType, mapper),
8822-
instantiateType((<ConditionalType>type).trueType, mapper), instantiateType((<ConditionalType>type).falseType, mapper),
8823-
type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
8884+
return getConditionalType(instantiateType((<ConditionalType>type).conditionType, mapper),
8885+
(<ConditionalType>type).trueType, (<ConditionalType>type).falseType,
8886+
type.aliasSymbol, type.aliasTypeArguments, mapper);
88248887
}
88258888

88268889
function instantiateType(type: Type, mapper: TypeMapper): Type {
@@ -8858,6 +8921,9 @@ namespace ts {
88588921
if (type.flags & TypeFlags.Conditional) {
88598922
return getConditionalTypeInstantiation(<ConditionalType>type, mapper);
88608923
}
8924+
if (type.flags & TypeFlags.Extends) {
8925+
return getExtendsType(instantiateType((<ExtendsType>type).checkType, mapper), instantiateType((<ExtendsType>type).extendsType, mapper));
8926+
}
88618927
}
88628928
return type;
88638929
}
@@ -20047,6 +20113,11 @@ namespace ts {
2004720113
checkSourceElement(node.type);
2004820114
}
2004920115

20116+
function checkConditionalType(node: ConditionalTypeNode) {
20117+
forEachChild(node, checkSourceElement);
20118+
checkTypeAssignableTo(getTypeFromTypeNode(node.conditionType), booleanType, node.conditionType);
20119+
}
20120+
2005020121
function isPrivateWithinAmbient(node: Node): boolean {
2005120122
return hasModifier(node, ModifierFlags.Private) && !!(node.flags & NodeFlags.Ambient);
2005220123
}
@@ -23687,6 +23758,11 @@ namespace ts {
2368723758
return checkSourceElement((<ParenthesizedTypeNode | TypeOperatorNode>node).type);
2368823759
case SyntaxKind.TypeOperator:
2368923760
return checkTypeOperator(<TypeOperatorNode>node);
23761+
case SyntaxKind.ConditionalType:
23762+
return checkConditionalType(<ConditionalTypeNode>node);
23763+
case SyntaxKind.BinaryType:
23764+
forEachChild(node, checkSourceElement);
23765+
return;
2369023766
case SyntaxKind.JSDocAugmentsTag:
2369123767
return checkJSDocAugmentsTag(node as JSDocAugmentsTag);
2369223768
case SyntaxKind.JSDocTypedefTag:

src/compiler/declarationEmitter.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,8 @@ namespace ts {
456456
return emitParenType(<ParenthesizedTypeNode>type);
457457
case SyntaxKind.TypeOperator:
458458
return emitTypeOperator(<TypeOperatorNode>type);
459+
case SyntaxKind.BinaryType:
460+
return emitBinaryType(<BinaryTypeNode>type);
459461
case SyntaxKind.IndexedAccessType:
460462
return emitIndexedAccessType(<IndexedAccessTypeNode>type);
461463
case SyntaxKind.MappedType:
@@ -548,16 +550,14 @@ namespace ts {
548550
}
549551

550552
function emitConditionalType(node: ConditionalTypeNode) {
551-
emitType(node.checkType);
552-
write(" extends ");
553-
emitType(node.extendsType);
553+
emitType(node.conditionType);
554554
write(" ? ");
555555
emitType(node.trueType);
556556
write(" : ");
557557
emitType(node.falseType);
558558
}
559559

560-
function emitParenType(type: ParenthesizedTypeNode) {
560+
function emitParenType(type: ParenthesizedTypeNode) {
561561
write("(");
562562
emitType(type.type);
563563
write(")");
@@ -569,6 +569,12 @@ namespace ts {
569569
emitType(type.type);
570570
}
571571

572+
function emitBinaryType(node: BinaryTypeNode) {
573+
emitType(node.left);
574+
write(" extends ");
575+
emitType(node.right);
576+
}
577+
572578
function emitIndexedAccessType(node: IndexedAccessTypeNode) {
573579
emitType(node.objectType);
574580
write("[");

src/compiler/emitter.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,8 @@ namespace ts {
572572
return emitThisType();
573573
case SyntaxKind.TypeOperator:
574574
return emitTypeOperator(<TypeOperatorNode>node);
575+
case SyntaxKind.BinaryType:
576+
return emitBinaryType(<BinaryTypeNode>node);
575577
case SyntaxKind.IndexedAccessType:
576578
return emitIndexedAccessType(<IndexedAccessTypeNode>node);
577579
case SyntaxKind.MappedType:
@@ -1132,9 +1134,7 @@ namespace ts {
11321134
}
11331135

11341136
function emitConditionalType(node: ConditionalTypeNode) {
1135-
emit(node.checkType);
1136-
write(" extends ");
1137-
emit(node.extendsType);
1137+
emit(node.conditionType);
11381138
write(" ? ");
11391139
emit(node.trueType);
11401140
write(" : ");
@@ -1157,6 +1157,12 @@ namespace ts {
11571157
emit(node.type);
11581158
}
11591159

1160+
function emitBinaryType(node: BinaryTypeNode) {
1161+
emit(node.left);
1162+
write(" extends ");
1163+
emit(node.right);
1164+
}
1165+
11601166
function emitIndexedAccessType(node: IndexedAccessTypeNode) {
11611167
emit(node.objectType);
11621168
write("[");

0 commit comments

Comments
 (0)