Skip to content

Concrete object not assignable to a generic mapped type with partially concrete constraint #49063

@Andarist

Description

@Andarist

Bug Report

🔎 Search Terms

concrete object, assignable, generic mapped type, concrete constraint, partially

🕗 Version & Regression Information

  • This is the behavior in every version I tried (interestingly though the intersected variant of this target type, presented in the playground, became valid in TS 3.6)

⏯ Playground Link

Playground link with relevant code

💻 Code

type ExtractEvent<
  TEvent extends { type: string },
  TEventType extends TEvent["type"]
> = TEvent extends {
  type: TEventType;
}
  ? TEvent
  : never;

type TransitionConfig<TContext, TEvent extends { type: string }> = {
  actions?: {
    type: string;
  };
};

type IntersectedTransitionConfigMap<TContext, TEvent extends { type: string }> = {
  [K in TEvent["type"]]?: TransitionConfig<TContext, ExtractEvent<TEvent, K>>;
} & {
  "*": TransitionConfig<TContext, TEvent>;
};

type TransitionConfigMap<TContext, TEvent extends { type: string }> = {
  [K in TEvent["type"] | "*"]?: K extends "*"
    ? TransitionConfig<TContext, TEvent>
    : TransitionConfig<TContext, ExtractEvent<TEvent, K>>;
};

export function genericFn<TEvent extends { type: string }>() {
  const wildcardTransitionConfig = {
    "*": { actions: { type: "someAction" } },
  } as const;

  // this should be assignable, in the same way as the following assignment is OK
  let test: TransitionConfigMap<
    { counter: number },
    { type: TEvent["type"] }
  > = {} as typeof wildcardTransitionConfig;

  // concrete prop is assignable to the concrete prop of this mapped type
  test["*"] = {} as typeof wildcardTransitionConfig["*"];

  // similar intersected type accepts this concrete object
  let test2: IntersectedTransitionConfigMap<
    { counter: number },
    { type: TEvent["type"] }
  > = {} as typeof wildcardTransitionConfig;
}

🙁 Actual behavior

The concrete object is not assignable to this generic mapped with a partially concrete constraint

🙂 Expected behavior

This should be allowed, or the second assignment (the one to test['*']) should also be considered as invalid.


The problem originates here where this relation check fails:

indexedAccessType // { readonly "*": { readonly actions: { readonly type: "someAction"; }; }; }[(("*" | TEvent["type"]) & "*") & K]
templateType // (K extends "*" ? TransitionConfig<{ counter: number; }, { type: TEvent["type"]; }> : TransitionConfig<{ counter: number; }, ExtractEvent<{ type: TEvent["type"]; }, K>>) | undefined
isRelatedTo(indexedAccessType, templateType, RecursionFlags.Both, reportErrors) // 0

Which leads us to checking if a type is assignable to a conditional type and that is rarely even possible, as only limited scenarios are even checked for such a case (here)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs InvestigationThis issue needs a team member to investigate its status.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions