@@ -631,6 +631,7 @@ namespace ts {
631631 const literalTypes = createMap<LiteralType>();
632632 const indexedAccessTypes = createMap<IndexedAccessType>();
633633 const substitutionTypes = createMap<SubstitutionType>();
634+ const structuralTags = createMap<StructuralTagType>();
634635 const evolvingArrayTypes: EvolvingArrayType[] = [];
635636 const undefinedProperties = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;
636637
@@ -3799,6 +3800,10 @@ namespace ts {
37993800 if (type.flags & TypeFlags.Substitution) {
38003801 return typeToTypeNodeHelper((<SubstitutionType>type).typeVariable, context);
38013802 }
3803+ if (type.flags & TypeFlags.StructuralTag) {
3804+ context.approximateLength += 4;
3805+ return createTypeOperatorNode(SyntaxKind.TagKeyword, typeToTypeNodeHelper((type as StructuralTagType).type, context));
3806+ }
38023807
38033808 return Debug.fail("Should be unreachable.");
38043809
@@ -8103,6 +8108,9 @@ namespace ts {
81038108 if (t.flags & TypeFlags.Substitution) {
81048109 return getBaseConstraint((<SubstitutionType>t).substitute);
81058110 }
8111+ if (t.flags & TypeFlags.StructuralTag) {
8112+ return unknownType;
8113+ }
81068114 return t;
81078115 }
81088116 }
@@ -9924,10 +9932,10 @@ namespace ts {
99249932 return links.resolvedType;
99259933 }
99269934
9927- function addTypeToIntersection(typeSet: Map<Type>, includes: TypeFlags, type: Type) {
9935+ function addTypeToIntersection(typeSet: Map<Type>, includes: TypeFlags, type: Type, tagSet: Map<StructuralTagType> ) {
99289936 const flags = type.flags;
99299937 if (flags & TypeFlags.Intersection) {
9930- return addTypesToIntersection(typeSet, includes, (<IntersectionType>type).types);
9938+ return addTypesToIntersection(typeSet, includes, (<IntersectionType>type).types, tagSet );
99319939 }
99329940 if (isEmptyAnonymousObjectType(type)) {
99339941 if (!(includes & TypeFlags.IncludesEmptyObject)) {
@@ -9939,6 +9947,9 @@ namespace ts {
99399947 if (flags & TypeFlags.AnyOrUnknown) {
99409948 if (type === wildcardType) includes |= TypeFlags.IncludesWildcard;
99419949 }
9950+ else if (flags & TypeFlags.StructuralTag) {
9951+ tagSet.set(type.id.toString(), type as StructuralTagType);
9952+ }
99429953 else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !typeSet.has(type.id.toString())) {
99439954 if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) {
99449955 // We have seen two distinct unit types which means we should reduce to an
@@ -9954,9 +9965,23 @@ namespace ts {
99549965
99559966 // Add the given types to the given type set. Order is preserved, freshness is removed from literal
99569967 // 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();
99589971 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);
99609985 }
99619986 return includes;
99629987 }
@@ -10258,6 +10283,11 @@ namespace ts {
1025810283 case SyntaxKind.ReadonlyKeyword:
1025910284 links.resolvedType = getTypeFromTypeNode(node.type);
1026010285 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;
1026110291 default:
1026210292 throw Debug.assertNever(node.operator);
1026310293 }
@@ -10272,6 +10302,19 @@ namespace ts {
1027210302 return type;
1027310303 }
1027410304
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+
1027510318 /**
1027610319 * Returns if a type is or consists of a JSLiteral object type
1027710320 * In addition to objects which are directly literals,
@@ -11706,6 +11749,10 @@ namespace ts {
1170611749 return sub;
1170711750 }
1170811751 }
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+ }
1170911756 return type;
1171011757 }
1171111758
@@ -13422,6 +13469,9 @@ namespace ts {
1342213469 if (flags & TypeFlags.Substitution) {
1342313470 return isRelatedTo((<SubstitutionType>source).substitute, (<SubstitutionType>target).substitute, /*reportErrors*/ false);
1342413471 }
13472+ if (flags & TypeFlags.StructuralTag) {
13473+ return isRelatedTo((<StructuralTagType>source).type, (<StructuralTagType>target).type, /*reportErrors*/ false);
13474+ }
1342513475 return Ternary.False;
1342613476 }
1342713477
@@ -13524,6 +13574,12 @@ namespace ts {
1352413574 }
1352513575 }
1352613576 }
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+ }
1352713583
1352813584 if (source.flags & TypeFlags.TypeVariable) {
1352913585 if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) {
@@ -15727,6 +15783,9 @@ namespace ts {
1572715783 inferFromTypes((<IndexType>source).type, (<IndexType>target).type);
1572815784 contravariant = !contravariant;
1572915785 }
15786+ else if (source.flags & TypeFlags.StructuralTag && target.flags & TypeFlags.StructuralTag) {
15787+ inferFromTypes((<StructuralTagType>source).type, (<StructuralTagType>target).type);
15788+ }
1573015789 else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) {
1573115790 const empty = createEmptyObjectTypeFromStringLiteral(source);
1573215791 contravariant = !contravariant;
0 commit comments