@@ -631,6 +631,7 @@ namespace ts {
631
631
const literalTypes = createMap<LiteralType>();
632
632
const indexedAccessTypes = createMap<IndexedAccessType>();
633
633
const substitutionTypes = createMap<SubstitutionType>();
634
+ const structuralTags = createMap<StructuralTagType>();
634
635
const evolvingArrayTypes: EvolvingArrayType[] = [];
635
636
const undefinedProperties = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;
636
637
@@ -3799,6 +3800,10 @@ namespace ts {
3799
3800
if (type.flags & TypeFlags.Substitution) {
3800
3801
return typeToTypeNodeHelper((<SubstitutionType>type).typeVariable, context);
3801
3802
}
3803
+ if (type.flags & TypeFlags.StructuralTag) {
3804
+ context.approximateLength += 4;
3805
+ return createTypeOperatorNode(SyntaxKind.TagKeyword, typeToTypeNodeHelper((type as StructuralTagType).type, context));
3806
+ }
3802
3807
3803
3808
return Debug.fail("Should be unreachable.");
3804
3809
@@ -8103,6 +8108,9 @@ namespace ts {
8103
8108
if (t.flags & TypeFlags.Substitution) {
8104
8109
return getBaseConstraint((<SubstitutionType>t).substitute);
8105
8110
}
8111
+ if (t.flags & TypeFlags.StructuralTag) {
8112
+ return unknownType;
8113
+ }
8106
8114
return t;
8107
8115
}
8108
8116
}
@@ -9924,10 +9932,10 @@ namespace ts {
9924
9932
return links.resolvedType;
9925
9933
}
9926
9934
9927
- function addTypeToIntersection(typeSet: Map<Type>, includes: TypeFlags, type: Type) {
9935
+ function addTypeToIntersection(typeSet: Map<Type>, includes: TypeFlags, type: Type, tagSet: Map<StructuralTagType> ) {
9928
9936
const flags = type.flags;
9929
9937
if (flags & TypeFlags.Intersection) {
9930
- return addTypesToIntersection(typeSet, includes, (<IntersectionType>type).types);
9938
+ return addTypesToIntersection(typeSet, includes, (<IntersectionType>type).types, tagSet );
9931
9939
}
9932
9940
if (isEmptyAnonymousObjectType(type)) {
9933
9941
if (!(includes & TypeFlags.IncludesEmptyObject)) {
@@ -9939,6 +9947,9 @@ namespace ts {
9939
9947
if (flags & TypeFlags.AnyOrUnknown) {
9940
9948
if (type === wildcardType) includes |= TypeFlags.IncludesWildcard;
9941
9949
}
9950
+ else if (flags & TypeFlags.StructuralTag) {
9951
+ tagSet.set(type.id.toString(), type as StructuralTagType);
9952
+ }
9942
9953
else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !typeSet.has(type.id.toString())) {
9943
9954
if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) {
9944
9955
// We have seen two distinct unit types which means we should reduce to an
@@ -9954,9 +9965,23 @@ namespace ts {
9954
9965
9955
9966
// Add the given types to the given type set. Order is preserved, freshness is removed from literal
9956
9967
// types, duplicates are removed, and nested types of the given kind are flattened into the set.
9957
- function addTypesToIntersection(typeSet: Map<Type>, includes: TypeFlags, types: ReadonlyArray<Type>) {
9968
+ function addTypesToIntersection(typeSet: Map<Type>, includes: TypeFlags, types: ReadonlyArray<Type>, tagSet?: Map<StructuralTagType> | undefined) {
9969
+ const isTopLevel = !tagSet;
9970
+ tagSet = tagSet || createMap();
9958
9971
for (const type of types) {
9959
- includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type));
9972
+ includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type), tagSet);
9973
+ }
9974
+ if (isTopLevel && tagSet.size) {
9975
+ let tag: StructuralTagType;
9976
+ if (tagSet.size === 1) {
9977
+ tag = tagSet.values().next().value;
9978
+ }
9979
+ else {
9980
+ const tagTypes: Type[] = [];
9981
+ tagSet.forEach(t => tagTypes.push(t.type));
9982
+ tag = getStructuralTagForType(getIntersectionType(tagTypes));
9983
+ }
9984
+ typeSet.set(tag.id.toString(), tag);
9960
9985
}
9961
9986
return includes;
9962
9987
}
@@ -10258,6 +10283,11 @@ namespace ts {
10258
10283
case SyntaxKind.ReadonlyKeyword:
10259
10284
links.resolvedType = getTypeFromTypeNode(node.type);
10260
10285
break;
10286
+ case SyntaxKind.TagKeyword:
10287
+ const aliasSymbol = getAliasSymbolForTypeNode(node);
10288
+ const aliasParams = getTypeArgumentsForAliasSymbol(aliasSymbol);
10289
+ links.resolvedType = getStructuralTagForType(getTypeFromTypeNode(node.type), aliasSymbol, aliasParams);
10290
+ break;
10261
10291
default:
10262
10292
throw Debug.assertNever(node.operator);
10263
10293
}
@@ -10272,6 +10302,19 @@ namespace ts {
10272
10302
return type;
10273
10303
}
10274
10304
10305
+ function getStructuralTagForType(type: Type, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) {
10306
+ const tid = "" + getTypeId(type);
10307
+ if (structuralTags.has(tid)) {
10308
+ return structuralTags.get(tid)!;
10309
+ }
10310
+ const tag = createType(TypeFlags.StructuralTag) as StructuralTagType;
10311
+ tag.type = type;
10312
+ tag.aliasSymbol = aliasSymbol;
10313
+ tag.aliasTypeArguments = aliasTypeArguments;
10314
+ structuralTags.set(tid, tag);
10315
+ return tag;
10316
+ }
10317
+
10275
10318
/**
10276
10319
* Returns if a type is or consists of a JSLiteral object type
10277
10320
* In addition to objects which are directly literals,
@@ -11706,6 +11749,10 @@ namespace ts {
11706
11749
return sub;
11707
11750
}
11708
11751
}
11752
+ if (flags & TypeFlags.StructuralTag) {
11753
+ const newType = instantiateType((type as StructuralTagType).type, mapper);
11754
+ return newType !== type ? getStructuralTagForType(newType, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type;
11755
+ }
11709
11756
return type;
11710
11757
}
11711
11758
@@ -13422,6 +13469,9 @@ namespace ts {
13422
13469
if (flags & TypeFlags.Substitution) {
13423
13470
return isRelatedTo((<SubstitutionType>source).substitute, (<SubstitutionType>target).substitute, /*reportErrors*/ false);
13424
13471
}
13472
+ if (flags & TypeFlags.StructuralTag) {
13473
+ return isRelatedTo((<StructuralTagType>source).type, (<StructuralTagType>target).type, /*reportErrors*/ false);
13474
+ }
13425
13475
return Ternary.False;
13426
13476
}
13427
13477
@@ -13524,6 +13574,12 @@ namespace ts {
13524
13574
}
13525
13575
}
13526
13576
}
13577
+ else if (target.flags & TypeFlags.StructuralTag) {
13578
+ if (source.flags & TypeFlags.StructuralTag) {
13579
+ return isRelatedTo((source as StructuralTagType).type, (target as StructuralTagType).type, reportErrors);
13580
+ }
13581
+ return Ternary.False;
13582
+ }
13527
13583
13528
13584
if (source.flags & TypeFlags.TypeVariable) {
13529
13585
if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) {
@@ -15727,6 +15783,9 @@ namespace ts {
15727
15783
inferFromTypes((<IndexType>source).type, (<IndexType>target).type);
15728
15784
contravariant = !contravariant;
15729
15785
}
15786
+ else if (source.flags & TypeFlags.StructuralTag && target.flags & TypeFlags.StructuralTag) {
15787
+ inferFromTypes((<StructuralTagType>source).type, (<StructuralTagType>target).type);
15788
+ }
15730
15789
else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) {
15731
15790
const empty = createEmptyObjectTypeFromStringLiteral(source);
15732
15791
contravariant = !contravariant;
0 commit comments