00697-extreme-tag

Back

The core idea is simple:

A tagged token is an object that has a symbol key, which in turn saves the applied tags + the type value that was tagged. After that is decided, the 'fun' begins. We need assignable cases, as in string extends Tag<string, 'hi!'>

We need to handle edge cases that are greedy to checks, such as never, unknown and any, as to prevent string extends any and such

It helpes to play around with the test results to see what each call results into

Helpers

type IsNull<T> = [T] extends [null] ? true : false

type IsUnknown<T> = (
	unknown extends T // `T` can be `unknown` or `any`
	  ? IsNull<T> extends false // `any` can be `null`, but `unknown` can't be
	    ? true
	    : false
	  : false
)

// This could be different, its the api of choice to make it obvious on the eye
type Tagged = { [K in symbol]: { _______________tags___________: string[], __value__: any } }

/**
 * A simply T extends Tagged is not enough,
 * but because how TS can resolve the type
 * after we go for the true branch on IsTagged<T>
 * you'll often see `IsTagged` followed by `T extends Tagged`
 * to make TS happy and let us access the internal structure
 *
 * Edge cases:
 *
 * any | never causes either `boolean` or `true` to be returned
 */
type IsTagged<
  T,

  _Ref = Extract<T, Tagged>,
> =
  [_Ref] extends [never]
    ? false
    : IsAny<_Ref> extends true
      ? false
      : symbol extends keyof _Ref
        ? '__value__' extends keyof _Ref[symbol]
          ? '_______________tags___________' extends keyof _Ref[symbol]
            ? true
            : false
          : false
        : false

type GetTaggedValue<T, Fallback = T> =
  IsTagged<T> extends true
    ? T extends Tagged
      ? T[symbol]['__value__']
      : Fallback
    : Fallback

Get Tags

type GetTagsInner<B> =
    IsTagged<B> extends true
      ? B extends Tagged
        ? B[symbol]['_______________tags___________']
        : []
      : []

type GetTags<
  B,

  _RESULT = GetTagsInner<
    Extract<NonNullable<B>, Tagged>
  >,
> =
  IsUnion<NonNullable<B>> extends true
    ? [Exclude<NonNullable<B>, Tagged | GetTaggedValue<B, never>>] extends [never]
        ? _RESULT
        : []
    : _RESULT

Here we can't simply return _RESULT, as if we have:

GetTags<Tag<0, 'foo'> | 1>

we don't really have tags, as we have a non-tagged union, but _RESULT gets us ['foo'], as we narrow our type before calling GetTagsInner, which is correct to not get us [] | [] scenarios

Because of this, we need to verify if:

  GetTags<
       | Tag<0, 'foo'>
       | Tag<1, 'foo'>
    >

is a valid call, meaning _RESULT = ['foo']

AND! Even if it wasn't, because of the assign-ability requirement, Tag<0, 'foo'> ==> 1 | Tagged<1> which is a union by itself!!

As a conclusion, need a further check after we know its a union:

Do we still have anything left on this union if we remove any Tagged trace of it?

 ==> To remove the trace, we need to Exclude BOTH the `Tagged` type AND its value

If we do have anything left, we have a bad union

Tagging

Here's where the magic happens, we have the main Tag<> with 2 inner helpers to facilitate breaking the actions in pieces we can grasp

type ResolveTag<Target, T extends string> =
  | (
    & {
      [K in symbol]: {
        __value__: GetTaggedValue<Target>

        _______________tags___________:
        GetTags<Target> extends unknown[]
          ? [...GetTags<Target>, T]
          : [T]
      }
    }
    & (
        // Merge Target to Tagged if obj
        [Target] extends [never]
          ? unknown
          : IsAny<Target> extends true
            ? unknown
            : Target extends Record<string, unknown>
              ? IsTagged<Target> extends true
                ? unknown
                : Target
              : unknown
      )
  )
  | (
    IsAny<Target> extends true
      ? never
      : Target extends Record<string, unknown>
        ? never
        : IsTagged<Target> extends true
          ? never
          : IsUnknown<Target> extends true
            ? never
            : NonNullable<Target>
  )

/**
 * Here we split calls with edge-awareness:
 *
 * - If we receive a tagged item, we need to further Tag it,
 *   NOT create variant unions out of it. For every other type inside our union,
 *   we simply return it back
 *
 * - Never and any are edged-out to ensure our nullable check
 *   picks up properly
 *
 * - Otherwise, ResolveTag ensures the action
 */
type TagInner<B, T extends string> =
  IsTagged<B> extends true
    ? ResolveTag<Extract<B, Tagged>, T> | Exclude<B, Tagged> // prevent distribution to create more tag unions
    : [B] extends [never]
        ? ResolveTag<B, T>
        : IsAny<B> extends true
          ? ResolveTag<B, T>
          : B extends undefined | null
            ? B
            : ResolveTag<B, T>

/**
 * Get the undefined | null out of the way immediately!
 *
 * Otherwise is way harder to add filters for them
 * on the much more complex inner logic
 */
type Tag<B, T extends string> =
  IsUnion<B> extends true
    ? NonNullable<TagInner<B, T>>
    : TagInner<B, T>

To ensure assign-ability (string extends Tag for example), we MUST have a union, because string itself needs to be inside Tag<string> for that to be true

(Always good to remember: X extends Y == does Y contain X?)

Obviously we cannot simply do Tagged | T, so the union is only returned if Target is NOT:

Having that extensive ruleset out of the way,the main Tagged type:

REMOVING THE TAG

The concept here is simple:

Check if we tagged and dig for the value, otherwise, simply return the non-tagged type

Its important to note we can't use GetTaggedValue directly because of the Unions inside the Tag result

Doing it like this ensures UnTag<number | Tagged<number>> = number | number = number

type UnTag<B> = IsTagged<B> extends true
  ? B extends Tagged
    ? B[symbol]['__value__']
    : B
  : B

Has Tag

Get the tags and see if their union matches the test value

Because TS can't really infer GetTags<X> as a list with certainty, even if we do extends string[] as constraint, we add narrow for it with the extends

type HasTagInner<
  B,
  T extends string,
  _Tags = GetTags<NonNullable<B>>
> =
  _Tags extends string[]
    ? T extends _Tags[number]
      ? true
      : false
    : false

type HasTag<B, T extends string> = HasTagInner<B, T> extends true ? true : false

Has Tags

Important: Checks if the tags were applied in succession, as requirement

The name choice here is BAD. Oh well!

To check if we have the correct order applied, we need:

  1. control variable to tell recursive calls we are in the middle of the tag streak
  2. Properly control our end cases:
    • if we get to the end with 0 tags in our list = yes, we have a streak!
    • if we find a tag that is not in our streak (CheckStarted = true + NextTag != TagToCheck) we restart the whole check, sending all the tags again to ensure we can streak them all
type HasTagsInner<
  B,
  T extends readonly string[],

  _TAGS = GetTags<B>,

  CheckStarted extends boolean = false,
> =
  _TAGS extends readonly [infer NextTag extends string, ...infer Rest extends readonly string[]]
    ? T extends readonly [infer TagToCheck extends string, ...infer RestTags extends readonly string[]]
      ? NextTag extends TagToCheck
        ? HasTagsInner<B, RestTags, Rest, true>
        : CheckStarted extends true
          ? false // streak break
          : HasTagsInner<B, [TagToCheck, ...RestTags], Rest> // we need to start the streak all over
      : true
    : T['length'] extends 0
      ? true // we consumed all the tags successfully!
      : false

type HasTags<
  B,
  T extends readonly string[],
> = 
  true extends HasTagsInner<B, T>
    ? true
    : false

Has Exact Tags

Simply use our HasTags helper if we have the same amount of tags added as tags to check

type HasExactTags<
  B,
  T extends readonly string[], 
  
  _TAGS = GetTags<B>
> =
  _TAGS extends unknown[]
    ? _TAGS['length'] extends T['length']
      ? HasTags<B, T>
      : false
    : false

I know there's a few slick solutions for this already, but hopefully i managed to breakdown the ideia itself for anyone struggling with it. I'm 100% sure this solution can be optimized to reduce clutter and bad checks, but i think it shows the bigger picture successfully. Let me know what ya think :)

Solution by fabiosenracorrea #35999

const smb = Symbol();
type TagData<O, _T> = [O];
type Tagged<O, T> = { [smb]?: TagData<O, T> };
type Every<B, F, T = B> = F extends B ? F : T;
type ReplaceAN<T> = [T, 0] extends [never, 0] | [1, T] ? {} : T;

type IsIncludes<V, T, V0 = V, T0 = T> = T extends readonly []
  ? true
  : [V, T] extends [[infer X, ...infer XX], [infer Y, ...infer YY]]
  ? [X, Y] extends [Y, X]
    ? IsIncludes<XX, YY, V0, T0>
    : V0 extends [unknown, ...infer V1]
    ? IsIncludes<V1, T0, V1, T0>
    : false
  : false;

type GetTagsInner<B> = B extends Tagged<unknown, infer Ts>
  ? Ts extends string[]
    ? Ts
    : []
  : [];

type GetTags<B> = Every<GetTagsInner<ReplaceAN<B>>, []>;

type TagInner<B, B0, T> = [B] extends [null | undefined]
  ? B
  : [typeof smb, B] extends [
      keyof B,
      Tagged<infer OT, infer Ts extends string[]>
    ]
  ? ReplaceAN<OT> & Tagged<OT, [...Ts, T]>
  : B & Tagged<B0, [T]>;

type Tag<B, T extends string> = TagInner<ReplaceAN<B>, B, T>;

type UnTag<B> = [typeof smb, B] extends [keyof B, Tagged<infer O, 0>] ? O : B;

type HasTagInner<TT, T> = TT extends string[]
  ? T extends TT[number]
    ? true
    : false
  : false;

type HasTag<B, T extends string> = Every<
  HasTagInner<GetTags<B>, T>,
  false,
  true
>;

type HasTags<B, T extends readonly string[]> = IsIncludes<GetTags<B>, T>;

type HasExactTags<B, T extends readonly string[]> = GetTags<B> extends T
  ? true
  : false;

Solution by alexandroppolus #35294

// Refer to https://github.com/type-challenges/type-challenges/issues/22316

// ===========
// == Utils ==
// ===========

// A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.ts(1170)
declare const UniqueSymbol: unique symbol
type UniqueSymbolType = typeof UniqueSymbol

type _FilterEmpty<B, D> =
  Equal<B, null> extends true ? null :
    Equal<B, undefined> extends true ? undefined :
      D

type _PendingType = 1 // <T>() => T
type _StoreTagsByStringArray<TAGS extends string[]> = {
  // The expression usage with `_{PendingType}` is meant to enable assignment among each tag.
  // [UniqueSymbol]?: ([ TAGS ] | _PendingType) & _PendingType // It could solve issue① while comes another issue.
  [UniqueSymbol]?: (TAGS | _PendingType) & _PendingType // Utilize issue① below
}

type _Includes<A extends readonly unknown[], B extends readonly unknown[]> =
  A extends [...B, ...unknown[]]
    ? true
    : A extends [unknown, ...infer Rest]
      ? _Includes<Rest, B>
      : false

type _Union2Intersection<U> =
  (U extends unknown ? (arg: U) => void : never) extends
  (arg: infer I) => void ? I : never

type _GetTags<B> = [B] extends [never] ? [] : B extends _StoreTagsByStringArray<infer Tags extends string[]>
  ? Tags extends (infer S extends string[]) & _PendingType // Issue①: it would infer to `TAGS & _PendingType`.
    ? S
    : []
  : []

// ===========
// == Start ==
// ===========

type GetTags<B> = _Union2Intersection<(_GetTags<B>)> extends infer Result
  ? Result extends never
    ? []
    : Result extends string[]
      ? Result
      : never
  : never

type Tag<B, T extends string, _UB = UnTag<B>> = _FilterEmpty<B, _UB & _StoreTagsByStringArray<[ ...GetTags<B>, T ]>>

type UnTag<B> = _FilterEmpty<B, Omit<B, UniqueSymbolType>>

type HasTag<B, T extends string> = _Includes<GetTags<B>, [T]>
type HasTags<B, T extends readonly string[]> = _Includes<GetTags<B>, T>
type HasExactTags<B, T extends readonly string[]> = Equal<GetTags<B>, T>

Solution by lvjiaxuan #22739

/**
 * 版本一
 * 清晰、精干,但是有未通过的测试用例(GetTags 处理 union 的部分)
 * 这是我比较推崇的一版,比较适合学习
 */

// ---- Start: Structures ----

declare const UniqueSymbol: unique symbol

type UniqueSymbolType = typeof UniqueSymbol

type TagsWrapper<B, TGS> = UniqueSymbolType | (UniqueSymbolType & [B, TGS])

type TagsBag<B, TGS> = {
  [UniqueSymbol]?: TagsWrapper<B, TGS>
}

// ---- End: Structures ----

// ---- Start: Get Tags ----

type GetTags<B> = 
  Equal<B, never> extends true ? [] :
  B extends TagsBag<unknown, infer Tags extends string[]>
  ? Equal<Tags, string[]> extends true
    ? []
    : Tags
  : []

// ---- End: Get Tags ----

// ---- Start: Tag and UnTag ----

type PassEmptyValue<B, D> = 
  Equal<B, null> extends true ? null :
  Equal<B, undefined> extends true ? undefined :
  D

type Tag<B, TG> = PassEmptyValue<B, UnTag<B> & TagsBag<UnTag<B>, [...GetTags<B>, TG]>>

type UnTag<B> = PassEmptyValue<B, Omit<B, UniqueSymbolType>>

// ---- End: Tag and UnTag ----

// ---- Start: Other Methods ----

type Includes<A extends readonly unknown[], B extends readonly unknown[]> =
  A extends [...B, ...unknown[]]
  ? true
  : A extends [unknown, ...infer Rest]
    ? Includes<Rest, B>
    : false

type HasTag<B, T extends string> = Includes<GetTags<B>, [T]>
type HasTags<B, T extends readonly string[]> = Includes<GetTags<B>, T>
type HasExactTags<B, T extends readonly string[]> = Equal<GetTags<B>, T>

// ---- End: Other Methods ----

playground for version 1

/**
 * 版本二
 * 这一版是在版本一的基础上做了修改,将 GetTags 的返回值由交集变为并集,纯粹是为了满足测试用例的需要,增加了一些额外的逻辑
 * 学完版本一之后,如果非得要通关,可以使用这一版
 */

// ---- Start: Structures ----

declare const UniqueSymbol: unique symbol

type UniqueSymbolType = typeof UniqueSymbol

type TagsWrapper<B, TGS> = UniqueSymbolType | (UniqueSymbolType & [B, TGS])

type TagsBag<B, TGS> = {
  [UniqueSymbol]?: TagsWrapper<B, TGS>
}

// ---- End: Structures ----

// ---- Start: Get Tags ----

type Union2Intersection<U> =
  (U extends unknown ? (arg: U) => void : never) extends
  (arg: infer I) => void ? I : never

type _GetTags<B> = 
  Equal<B, never> extends true ? [] :
  B extends TagsBag<unknown, infer Tags extends string[]>
  ? Equal<Tags, string[]> extends true
    ? []
    : Tags
  : []

type GetTags<B> = 
  Union2Intersection<(_GetTags<B>)> extends infer Result
  ? Equal<Result, never> extends true
    ? []
    : Result extends string[]
      ? Result
      : never
  : never

// ---- End: Get Tags ----

// ---- Start: Wrap and Unwrap ----

type PassEmptyValue<B, D> = 
  Equal<B, null> extends true ? null :
  Equal<B, undefined> extends true ? undefined :
  D

type Tag<B, TG> = PassEmptyValue<B, UnTag<B> & TagsBag<UnTag<B>, [...GetTags<B>, TG]>>

type UnTag<B> = PassEmptyValue<B, Omit<B, UniqueSymbolType>>

// ---- End: Tag and UnTag ----

// ---- Start: Other Methods ----

type Includes<A extends readonly unknown[], B extends readonly unknown[]> =
  A extends [...B, ...unknown[]]
  ? true
  : A extends [unknown, ...infer Rest]
    ? Includes<Rest, B>
    : false

type HasTag<B, T extends string> = Includes<GetTags<B>, [T]>
type HasTags<B, T extends readonly string[]> = Includes<GetTags<B>, T>
type HasExactTags<B, T extends readonly string[]> = Equal<GetTags<B>, T>

// ---- End: Other Methods ----

playground for version 2


中文笔记

References

Solution by zhaoyao91 #22316

// =============  Helper Types  =============
type IsAny<T> = 0 extends 1 & T ? true : false
type IsNever<T> = [T] extends [never] ? true : false

type RemoveIndexSignature<T> = {
  [K in keyof T as Extract<string | number | symbol, K> extends never
    ? K
    : never]: T[K]
}

type GetRootTagObject<B> = US extends keyof B
  ? Required<RemoveIndexSignature<Required<B>[US]>>
  : never

// root object could be {}, but it's not a tag object, so treat root object specially
type GetTagObject<B> = B extends Record<any, unknown>
  ? Required<RemoveIndexSignature<B>>
  : never

// multiple tag is nested
type TagObject<P, T extends string> = (IsNever<P> extends true
  ? {
      [K in T]?: never
    }
  : {
      [K in keyof P]?: TagObject<GetTagObject<P[K]>, T>
    }) & {
  // make object with difference tag compatible
  [K in string]?: never
}

// ============= Function Types =============
type GetTags<
  B,
  O = IsAny<B> extends true ? never : GetRootTagObject<B>
> = IsNever<O> extends true
  ? []
  : [keyof O, ...GetTags<B, GetTagObject<O[keyof O]>>]

type Tag<
  B,
  T extends string,
  O = true extends IsAny<B> | IsNever<B> ? {} : B
> = [O] extends [null | undefined]
  ? O
  : {
      [K in Exclude<keyof O, US>]: O[K]
    } & {
      [K in US]?: TagObject<GetRootTagObject<O>, T>
    }

type UnTag<B> = {
  [K in keyof B as K extends US ? never : K]: B[K]
}

type HasTag<B, T extends string> = T extends GetTags<B>[number] ? true : false

type HasTags<
  B,
  T extends readonly string[],
  U extends string[] = GetTags<B> extends string[] ? GetTags<B> : []
> = U extends [...T, ...unknown[]]
  ? true
  : U extends [...unknown[], ...T]
  ? true
  : false

type HasExactTags<
  B,
  T extends readonly string[],
  U extends string[] = GetTags<B> extends string[] ? GetTags<B> : []
> = U extends T ? true : false

Solution by theoolee #19991

// your answers
type IsEqual<X,Y> =(<A>()=>A extends X ? 1 : 2) extends 
    (<B>()=>B extends Y ? 1 : 2)
    ? true
    : false ;

declare const a:unique symbol;
type Unique =typeof a;

declare const d: unique symbol;
type CommonValue=typeof d;

declare const b:unique symbol;
type NonRegular = typeof b;

type GetTagValue<
    Source> =
    Unique extends keyof Source
    ? Exclude<keyof Exclude<Source[Unique],undefined>,symbol|number>
    : never ;

type TagValueFromTagTuple<
    T extends readonly string[],
    TagValue extends string = never,
    PossiblyEmptyValue extends string = [TagValue] extends [never] ? '' : TagValue> =
    T extends readonly [infer R extends string,...infer U extends readonly string[]]
    ? U extends []
    ? TagValueFromTagTuple<U,`${PossiblyEmptyValue}${R}`>
    : TagValueFromTagTuple<U,`${PossiblyEmptyValue}${R}|`>
    : TagValue  ;

type TagTupleFromTagValue<
    TagValue extends string> =
    [TagValue] extends [never]
    ? []
    : TagValue extends `${infer R extends string}|${infer U extends string}`
    ? [R,...TagTupleFromTagValue<U>]
    : [TagValue] ;

type GetTags<
    Source> =
     [Source] extends [never] 
    ? []
    : 1 extends 2&Source
    ? []
    : TagTupleFromTagValue<GetTagValue<Source>> ;

type SetMarker<
    Source,
    NewTagValue extends string,
    OldTagValue extends string = 
        GetTagValue<Source>,
    TagValue extends string  = 
        [OldTagValue] extends [never] 
        ? `${NewTagValue}`
        : `${OldTagValue}|${NewTagValue}` ,
    Marker extends object = {[J in Unique]? : {[J in TagValue|CommonValue]? : true}}   > =
    NonRegular extends keyof Source  
    ? {[J in NonRegular] : Source[J]}&Marker
    : UnTag<Source>&Marker  ;      

type EffectiveSource<
    Source,
    > =
    [IsEqual<Source,{}>&IsEqual<Source,never>&IsEqual<Source,any>] extends [never] 
    ? {[J in NonRegular] : Source}
    : Source  ;
    
type Tag<
    Source, 
    NewTagValue extends string,
    > =
    IsEqual<Source,undefined>&IsEqual<Source,null> extends [never]
    ? Source
    : SetMarker<EffectiveSource<Source>,NewTagValue>  ;

type UnTag<
    Source,
    Marker ={[J in Unique&keyof Source]? :Source[J]}> =
    NonRegular extends keyof Source
    ? Source[NonRegular]
    : Source extends infer R&Marker
    ? R
    : Source  ;

type UnionMemberHasTag<
    Source, 
    TagValue extends string> =     
    Source extends Source
    ? [GetTagValue<Source>] extends [never]
    ? false
    : GetTagValue<Source> extends `${infer R}${TagValue}${infer U}`
    ? true
    : false
    : never;

type UnionMemberHasExactTag<
    Source,
    TagValue extends string> =
    Source extends Source
    ? IsEqual<GetTagValue<Source>,TagValue>
    : never ;

type HasTag<
    Source,
    TagValue extends string> = 
    UnionMemberHasTag<Source,TagValue> extends true 
    ? true 
    : false ;

type HasTags<
    Source, 
    TagTuple extends readonly string[]> =
    HasTag<Source,TagValueFromTagTuple<TagTuple>> ;

type HasExactTags<
    Source, 
    TagTuple extends readonly string[]> =
    TagTuple extends []
    ? true 
    : UnionMemberHasExactTag<Source,TagValueFromTagTuple<TagTuple>> extends true 
    ? true
    : false ;

Solution by justBadProgrammer #16642

type GetTags<B> = Splitted<ExtractedTags<B>>

type Tag<B, T extends string> = ReturnType<<I extends ExtractedTag<B>>() =>
    [I] extends [never] ? B
    : I extends Tagged<infer This, infer Tags extends string>
      ? Tagged<This, Appended<Tags, T>> extends infer NewTagged
        ? Equal<This | NewTagged, This> extends true ? NewTagged : This | NewTagged
      : never
    : never
  >

type UnTag<B> = ReturnType<<I extends ITag = ExtractedTag<B>>() =>
    [I] extends [never] ? B
    : I extends I ? I extends Tagged<infer This> ? This : never : never
  >

type HasTag<B, T extends string> = (
    & Record<`${any} ${T} ${any}`, true>
    & Record<string, false>
  )[` ${ExtractedTags<B>} `]

type HasTags<B, T extends readonly string[]> = (
    & Record<`${any} ${Joined<T>} ${any}`, true>
    & Record<string, false>
  )[` ${ExtractedTags<B>} `]

type HasExactTags<B, T extends readonly string[]> = (
    & Record<Joined<T>, true>
    & Record<string, false>
  )[ExtractedTags<B>]

/* Utilities */
type Tagged<This, Tag extends string = string> = ReturnType<<NewTag extends {[UNIQUE_SYMBOL]: [This, Tag]}
  >() => Equal<This & NewTag, This> extends true ? NewTag : ReturnType<<T extends This & NewTag>() => {[K in keyof T]: T[K]}>>

type ExtractedTag<B>
  = true extends Equal<B, any> | Equal<Extract<B, ITag>, never> ? Tagged<B, ''>
  : ExtractedOne<Extract<B, ITag>> extends infer S extends ITag
    ? S extends Tagged<infer This>
      ? true extends Equal<Exclude<B, S | This>, never> ? S : S | ExtractedTag<Exclude<B, S | This>>
    : never
  : never

type ExtractedTags<B> = Equal<B, any> extends true ? '' : ReturnType<<
    I extends ExtractedTag<B>, Tags extends [I] extends [never] ? '' : I extends I ? I[US][1] : never
  >() => Equal<Tags, ExtractedOne<Tags>> extends true ? Tags : ''>

type ExtractedOne<U>
  = (U extends U ? [Promise<U>] : U) extends {fill: ($: infer I) => void} ? Awaited<I>
  : never

type Splitted<Tags extends string, O extends string[] = []>
  = Tags extends `${infer L} ${infer R}` ? Splitted<R, L extends '' ? O : [...O, L]> : (
    & Record<'', O>
    & Record<string, [...O, Tags]>
  )[Tags]

type Joined<Tags, O extends string = ''>
  = Tags extends [`${infer L}`, ...infer R] ? Joined<R, `${O} ${L}`>
  : O extends ` ${infer R}` ? R : O

type Appended<Tags extends string, T extends string> = (
    & Record<'', T>
    & Record<string, (
      & Record<` ${T} `, Tags>
      & Record<string, `${Tags} ${T}`>
    )>[` ${Tags} `]
  )[string extends Tags ? '' : Tags]

Playground

Solution by teamchong #11465

// your answers
declare const TAG: unique symbol

type TagHelper<B, T extends string> = typeof TAG extends keyof B
  ? Omit<B, typeof TAG> & { [TAG]: TagHelper<B[typeof TAG], T> }
  : B & { [TAG]: { [K in T]: true } }

type Tag<B, T extends string> = B extends null | undefined ? B : TagHelper<B, T>

type GetTags<B> = typeof TAG extends keyof B
  ? [keyof Omit<B[typeof TAG], typeof TAG>, ...GetTags<B[typeof TAG]>]
  : []

type Keys<A extends readonly any[], RR = never> = A extends [infer L, ...infer R]
  ? Keys<R, RR | L>
  : RR

type HasTagsHelper<A, B extends readonly any[]> = B extends [infer L, ...infer R]
  ? L extends A
    ? HasTagsHelper<A, R>
    : false
  : true

type HasTags<B, T extends readonly string[]> = T extends []
  ? false
  : HasTagsHelper<Keys<GetTags<B>>, T>

type HasTag<B, T extends string> = HasTags<B, [T]>

type HasExactTags<B, T extends readonly string[]> = GetTags<B> extends T ? true : false

type UnTag<B> = Omit<B, typeof TAG>

Solution by ZangYuSong #7175

declare const uq: unique symbol 
declare const dummy: unique symbol
type UQ = typeof uq
type DUMMY = typeof dummy

type UU<T> = Exclude<T, undefined>

type GetTags<B>
    = Equal<B, any> extends true ? []
    : UQ extends keyof B ? [Exclude<keyof UU<B[UQ]>, UQ | DUMMY>, ...GetTags<UU<B[UQ]>>] : []

type TagRec<B, T extends string> 
    = UQ extends keyof B ? Omit<B, UQ> & {[uq]?: TagRec<UU<B[keyof B & UQ]>, T>}
    : B & {[uq]?: {[P in T | DUMMY]?: 1}}

type Tag<B, T extends string>
    = Equal<B, null> extends true ? B
    : Equal<B, undefined> extends true ? B
    : Equal<B, any> extends true ? TagRec<{dummy: 1}, T>
    : Equal<B, never> extends true ? TagRec<{dummy: 2}, T>
    : TagRec<B, T>

type Member<T extends any[], U> = U extends T[number] ? true : false
type PrefixUnion<T> = T extends [...infer L, infer R] ? T | PrefixUnion<L> : never
type Subseq<T, U>
     = U extends PrefixUnion<T> ? true
     : T extends [infer L, ...infer R] ? Subseq<R, U> : false

type UnTag<B> = UQ extends keyof B ? Omit<B, UQ> : B
type HasTag<B, T extends string> = (B extends any ? Member<GetTags<B>, T> : never) extends true ? true : false
type HasTags<B, T extends readonly string[]> = Subseq<GetTags<B>, T>
type HasExactTags<B, T extends readonly string[]> = GetTags<B> extends T ? true : false

Solution by okayu29 #6319

Try me!

Expectation changes

"Has tags" predicate assertion changed to be non positional

Current assertions will only pass if tags argument order matches tagging order

type Cases = [
  // Should only pass if tags arguments is ["bar", "foo"]
  Expect<Equal<HasTags<Tag<Tag<{}, "bar">, "foo">, ["foo", "bar"]>, false>>,
  // Should only pass if tags arguments is ["foo", "baz"] or ["foo", "baz", 'bar]
  Expect<Equal<HasTags<Tag<Tag<Tag<{}, "foo">, "baz">, "bar">, ["foo", "bar"]>, false>>,
]

Solution will only fail if a non existent tag is provided

type Cases = [
  // Will pass for ["foo", "bar", "baz"], ["baz", "bar", "foo"], ...etc
  Expect<Equal<HasTags<Tag<Tag<Tag<{}, "foo">, "baz">, "bar">, ["foo", "bar"]>, true>>,
]

Solution

type Flatten<Type> = { [Key in keyof Type]: Type[Key] }

type UnionToIntersection<Type> = (
  Type extends any ? (wrapper: Type) => unknown :never
) extends (wrapper: infer WrappedType) => unknown ? WrappedType : never

type UnionToTuple<Type> = UnionToIntersection<
  Type extends any ? (wrapper: Type) => unknown :never
> extends (wrapper: infer WrappedType) => unknown
  ? [...UnionToTuple<Exclude<Type, WrappedType>> ,WrappedType]
  : []

/**
 * Copy of:
 *  import { Equal } from "@type-challenges/utils";
 */
type Equivalent<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false

declare const TagKey: unique symbol;

type Injectable<Target> = typeof TagKey extends keyof Target
  ? Omit<Target, never> // Preserve special types like any, unknown and never
  : Target

type InjectTag<Target, Type = {}> = Injectable<Target> & { [TagKey]?: Type }

type Tag<
  Target,
  Type extends string
> = Equivalent<Target, null> extends true
  ? null
  : Equivalent<Target, undefined> extends true
    ? undefined
    : Injectable<Target> extends Injectable<InjectTag<infer RootTarget, infer RootType>>
      ? InjectTag<
          RootTarget,
          Flatten<
            { [Key in keyof RootType]?: RootType[Key]} &
            { [Key in Type]?: Type } &
            Record<string, string>
          >
        >
      : InjectTag<
          Target,
          Flatten<
            { [Key in Type]?: Type } &
            Record<string, string>
          >
        >

type GetTagUnion<
  Target
> = Target extends InjectTag<unknown, infer Tags>
  ? keyof { [Key in keyof Tags as string extends Key ? never : Key]: Tags[Key] }
  : never

type GetCommonTagUnion<
  Target,
  TargetTuple = UnionToTuple<Target>
> = TargetTuple extends [infer CurrentTarget, ...infer RestTarget]
  ? RestTarget extends []
    ? GetTagUnion<CurrentTarget>
    : GetTagUnion<CurrentTarget> & GetCommonTagUnion<RestTarget[number]>
  : never

type GetTags<Target> = UnionToTuple<GetCommonTagUnion<Target>>

type UnTag<Target> = keyof Target extends never
  ? Target
  : Omit<Target, typeof TagKey>

type HasTag<
  Target,
  Type extends string
> = Type extends GetCommonTagUnion<Target>
  ? true
  : false

type HasTags<
  Target,
  Type extends ReadonlyArray<string>
> = Type[number] extends GetCommonTagUnion<Target>
  ? true
  : false

type HasExactTags<
  Target,
  Type extends ReadonlyArray<string>
> = GetCommonTagUnion<Target> extends Type[number]
  ? true
  : false

Solution by ianbunag #3768

type IsNever<T> = [T] extends [never] ? true : false
type IsAny<T> = 0 extends (1 & T) ? true : false
type Is<T, K> = [T] extends [K] ? [K] extends [T] ? true : false : false

type StartsWith<T extends any[], K extends any[]> = 
  T extends [infer T0, ...infer Tr]
  ? K extends [infer K0, ...infer Kr]
    ? Is<T0, K0> extends true
      ? StartsWith<Tr, Kr>
      : false
    : true
  : [] extends K
  ? true
  : false

type StartsWithTest = [
  Expect<Equal<StartsWith<[2,0,2,1], [2,0]>, true>>,
  Expect<Equal<StartsWith<[2,0,2,1], [2,1]>, false>>,
  Expect<Equal<StartsWith<[2,0,2,1], [2]>, true>>,
  Expect<Equal<StartsWith<[2,0,2,1], [2,0,2,1]>, true>>,
  Expect<Equal<StartsWith<[2,0,2,1], [2,0,2,1,2]>, false>>,
  Expect<Equal<StartsWith<[2,0,2,1], []>, true>>,
]


type SuccessionWith<T extends any[], K extends any[]> = 
  T extends [infer T0, ...infer Tr]
  ? K extends [infer K0, ...infer Kr]
    ? Is<T0, K0> extends true
      ? StartsWith<Tr, Kr>
      : SuccessionWith<Tr, K>
    : [] extends K
    ? true
    : false
  : false

type SuccessionWithTest = [
  Expect<Equal<SuccessionWith<[2,0,2,1], [1,2]>, false>>,
  Expect<Equal<SuccessionWith<[2,0,2,1], [2,0]>, true>>,
  Expect<Equal<SuccessionWith<[2,0,2,1], [2]>, true>>,
  Expect<Equal<SuccessionWith<[2,0,2,1], [2,0,2,1]>, true>>,
  Expect<Equal<SuccessionWith<[2,0,2,1], [1,2,0,2,1]>, false>>,
  Expect<Equal<SuccessionWith<[2,0,2,1], []>, true>>,
]

/**
 * Define tag container as function
 */
type Tags<T extends string[], B> = () => [T, B]

/**
 * Inject tags to any types except `null` and `undefined`.
 * 
 * `null | (null & Tags<T, null>)` wold be evaluated to `null`, so null cannot be labeled with tag(s), and `undefined` is the same.
 */
type SetTags<B, T extends string[]> =
  IsNever<B> extends true
  ? Tags<T, B>
  : IsAny<B> extends true
  ? Tags<T, B>
  : Is<B, unknown> extends true
  ? Tags<T, B>
  : B | (B & Tags<T, B>)

/**
 * Extract original type which already labeled with tags.
 * 
 * If `B` is an Union Type, only labeled with tag type would be returned as Union Type.
 */
type GetTagged<B> = B extends Tags<infer _, infer T> ? T : never

/**
 * Get tag string tuple from type `B`.
 * 
 * If `B` is an Union Type which all members are labeled with tags, this type returns Union Type of tag tuples.
 * When all members are labeled with exactly same tag(s), only one tuple will be retuned by Union Type's characteristics.
 */
type GetTagTupples<B> = 
  IsNever<Exclude<B, GetTagged<B>>> extends true
  ? IsNever<B> extends true
    ? []
    : IsAny<B> extends true
    ? []
    : B extends Tags<infer T, infer _>
    ? T
    : never
  : B extends Tags<infer T, never>
  ? T
  : []


type Tag<B, T extends string> = SetTags<UnTag<B>, [...GetTags<B>, T]>

type UnTag<B> = B extends infer X ? X extends Tags<infer _, infer K> ? K : X : never

/**
 * Get tag string tuple from type `B`.
 * 
 * If `B` is an Union Type which all members are labeled with tags, this type returns Union Type of tag tuples.
 * When all members are labeled with exactly same tag(s), only one tuple will be retuned by Union Type's characteristics.
 */
type GetTags<B> = GetTagTupples<Exclude<B, null | undefined>>

type HasTag<B, T extends string> = HasTags<B, [T]>

type HasTags<B, T extends readonly string[]> = SuccessionWith<GetTags<B>, [...T]> extends true ? true : false

type HasExactTags<B, T extends readonly string[]> = Is<GetTags<B>, T>

Solution by akipom #1947

This is a code-intensive task (although technically not very difficult), but it can be useful for organizing some kind of design-by-contract programming via types.

TS Playground.

declare const KEY: unique symbol;

type Tuple = readonly string[];

interface Tagged {
  readonly [KEY]?: unknown;
}

/**
 * Shift<["foo", "bar", "baz"]> = ["bar", "baz"].
 */
type Shift<T extends Tuple> = T extends [infer Head, ...infer Tail] ? Tail : [];

/**
 * StartWith<[], ["foo"]> = true.
 * StartWith<["foo"], ["foo", "bar"]> = true.
 * StartWith<["foo", "baz"], ["foo", "bar"]> = false.
 * StartWith<["foo", "bar"], ["foo", "bar", "qux"]> = true.
 */
type StartWith<S extends Tuple, T extends Tuple> = S extends []
  ? true
  : Equal<S[0], T[0]> extends false
  ? false
  : StartWith<Shift<S>, Shift<T>>;

/**
 * Includes<["foo", "bar"], ["quux", "foo", "bar", "qux"]> = true.
 * Includes<["foo"], ["bar", "qux"]> = false.
 */
type Includes<S extends Tuple, T extends Tuple> = S extends []
  ? true
  : T extends []
  ? false
  : Equal<S[0], T[0]> extends true
  ? StartWith<S, T>
  : Includes<S, Shift<T>>;

/**
 * GetStringProps<{ 0: 0; x?: 3 }> = 3.
 */
type GetStringProps<T> = Exclude<
  {
    [K in keyof T & string]: T[K];
  }[keyof T & string],
  undefined
>;

/**
 * GetStringKeys<{ 0: 0; x?: 3 }> = "x".
 */
type GetStringKeys<T> = Exclude<
  {
    [K in keyof T & string]: K;
  }[keyof T & string],
  undefined
>;

/**
 * GetTagsKey<null> = "".
 * GetTagsKey<Tag<string, "foo">> = "0foo".
 * GetTagsKey<Tag<Tag<string, "foo">, "bar">> = "0foo1bar"
 */
type GetTagsKey<
  V,
  TagsOrUndefined = [V] extends [Tagged] ? V[typeof KEY] : undefined,
  TagsKeyOrNever = GetStringKeys<Exclude<TagsOrUndefined, undefined>>
> = Equal<TagsKeyOrNever, never> extends true
  ? ""
  : Equal<TagsKeyOrNever, string> extends true
  ? ""
  : TagsKeyOrNever;

/**
 * GetTags<null> = [].
 * GetTags<any> = [].
 * GetTags<Tag<string, "foo">> = ["foo"].
 * GetTags<Tag<Tag<string, "foo">, "bar">> = ["foo", "bar"].
 * GetTags<Tag<Tag<Tag<{}, "foo">, "bar">, "baz">> = ["foo", "bar", "baz"].
 */
export type GetTags<
  V,
  TagsOrUndefined = [V] extends [Tagged] ? V[typeof KEY] : undefined,
  TagsOrNever = GetStringProps<Exclude<TagsOrUndefined, undefined>>
> = Equal<V, any> extends true
  ? []
  : Equal<TagsOrNever, never> extends true
  ? []
  : TagsOrNever extends Tuple
  ? TagsOrNever
  : [];

/**
 * Tag<number, "foo"> = number with tag "foo".
 * Tag<{ x: 0 }, "foo"> = { x: 0 } with tag "foo".
 * Tag<Tag<V, "foo">, "bar"> = V with tags "foo" and "bar".
 */
export type Tag<
  V,
  T extends string,
  Tags extends Tuple = GetTags<V>,
  TagsKey extends string = GetTagsKey<V>
> = Equal<V, null> extends true
  ? null
  : Equal<V, undefined> extends true
  ? undefined
  : (typeof KEY extends keyof V ? Omit<V, typeof KEY> : V) & {
      readonly [KEY]?: { 0: 0 } & {
        [K in `${TagsKey}${Tags["length"]}${T}`]?: [...Tags, T];
      };
    };

/**
 * UnTag<null> = null.
 * UnTag<undefined> = undefined.
 * UnTag<Tag<{}, "foo">> = {}.
 * UnTag<Tag<Tag<{ x: 0 }, "foo">, "bar">> = { x: 0 }.
 */
export type UnTag<V> = typeof KEY extends keyof V ? Omit<V, typeof KEY> : V;

/**
 * HasTag<null, "foo"> = false.
 * HasTag<Tag<{}, "foo">, "foo"> = true.
 * HasTag<Tag<any, "foo">, "foo"> = true.
 * HasTag<Tag<Tag<{}, "foo">, "bar">, "foo"> = true.
 * HasTag<Tag<Tag<symbol, "bar">, "foo">, "foo"> = true.
 * HasTag<Tag<Tag<{}, "bar">, "baz">, "foo"> = false.
 */
export type HasTag<V, T extends string> = Includes<[T], GetTags<V>>;

/**
 * HasTags<null, ["foo"]> = false.
 * HasTags<Tag<{}, "bar">, ["foo"]> = false.
 * HasTags<Tag<any, "bar">, ["foo"]> = false.
 * HasTags<Tag<{}, "foo">, ["foo"]> = true.
 * HasTags<Tag<any, "foo">, ["foo"]> = true.
 * HasTags<Tag<Tag<string, "foo">, "bar">, ["foo", "bar"]> = true.
 * HasTags<Tag<Tag<{}, "bar">, "foo">, ["foo", "bar"]> = false.
 * HasTags<Tag<Tag<Tag<{}, "baz">, "foo">, "bar">, ["foo", "bar"]> = true.
 * HasTags<Tag<Tag<Tag<{}, "foo">, "bar">, "baz">, ["foo", "bar"]> = true.
 * HasTags<Tag<Tag<Tag<{}, "foo">, "baz">, "bar">, ["foo", "bar"]> = false.
 */
export type HasTags<V, T extends Tuple> = Includes<T, GetTags<V>>;

/**
 * HasExactTags<0, []> = true.
 * HasExactTags<Tag<number, "foo">, ["foo"]> = true.
 * HasExactTags<Tag<{}, "foo">, ["bar"]> = false.
 * HasExactTags<Tag<Tag<any, "foo">, "bar">, ["foo", "bar"]> = true.
 * HasExactTags<Tag<Tag<Tag<{}, "foo">, "bar">, "baz">, ["foo", "bar"]> = false.
 * HasExactTags<Tag<Tag<Tag<{}, "foo">, "bar">, "baz">, ["foo", "bar", "baz"]> = true.
 */
export type HasExactTags<V, T extends Tuple> = Equal<GetTags<V>, T>;

Solution by uid11 #713