00697-extreme-tag

Back

// 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> =
  Tagged extends keyof IfNeverOrAny<B, unknown> ? (
    IfUndefined<B[Tagged], never> extends infer TagValue ? (
      IfUndefined<TagValue[keyof TagValue & string], never>
    ) : never
  ) : []

type Tag<B, T extends string> =
  [IfNeverOrAny<B, unknown>] extends [null | undefined] ? (
    B
  ) : { readonly [Tag in Tagged]?: GetTagValue<[...GetTags<B>, T]> }
  & { [K in keyof IfNeverOrAny<B, unknown> as K extends Tagged ? never : K]: B[K] } extends infer TaggedB ? (
    { [K in keyof TaggedB]: TaggedB[K] }
  ) : never

type UnTag<B> =
  Tagged extends keyof IfNeverOrAny<B, unknown> ? (
    { [K in keyof B as K extends Tagged ? never : K]: B[K] }
  ) : B

type HasTag<B, T extends string> =
  `\n${GetSerializedTags<B>}` extends `${string}\n${T}\n${string}` ? true : false

type HasTags<B, T extends readonly string[]> =
  `\n${GetSerializedTags<B>}` extends `${string}\n${SerializeTags<T>}${string}` ? true : false

type HasExactTags<B, T extends readonly string[]> =
  `${GetSerializedTags<B>}` extends `${SerializeTags<T>}` ? true : false

declare const TaggedSymbol: unique symbol

type Tagged = typeof TaggedSymbol
type IfUndefined<T, Replacement> = T extends undefined ? Replacement : T
type IfNeverOrAny<T, Replacement> = [T] extends [never] ? Replacement : 1 extends T & 0 ? Replacement : T

type GetSerializedTags<B> =
  Tagged extends keyof IfNeverOrAny<B, unknown> ? (
    IfUndefined<B[Tagged], never> extends infer TagValue ? (
      IfNeverOrAny<keyof TagValue & string, ''>
    ) : never
  ) : ''

type SerializeTags<Tags extends readonly string[]> =
  Tags extends [`${infer First}`, ...infer Rest extends string[]] ? (
    `${First}\n${SerializeTags<Rest>}`
  ) : ''

type GetTagValue<NewTag extends string[]> =
  { 1: 1 } & // we need this in order to assign to other Tagged object
  { [K in SerializeTags<NewTag>]?: NewTag } extends infer TagValue ? (
    { [K in keyof TagValue as K extends '' ? never : K]: TagValue[K] }
  ) : never

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