Home > Mobile >  Type 'ScopeType<T, unknown>' is not assignable to type 'ParentScopeType<S>
Type 'ScopeType<T, unknown>' is not assignable to type 'ParentScopeType<S>

Time:12-22

In my large compiler project, I have isolated the error code to this:

type UserType = {
  email: string
}

type ScopeType<S, P extends unknown = unknown> = {
  data: S
  parent?: P extends ScopeType<infer T, infer Q>
    ? ScopeType<T, Q>
    : never
}

type PossibleScopeType<ST> = ST extends ScopeType<
  infer S,
  infer P
>
  ? ST | (P extends ScopeType<infer A, infer B> ? (P | PossibleScopeType<P>) : never)
  : never

type ModuleType = {
  path: string
}

const scope: ScopeType<ModuleType> = {
  data: {
    path: './foo.x',
  },
}

const scope2: ScopeType<UserType, ScopeType<ModuleType>> = {
  data: {
    email: '[email protected]',
  },
  parent: scope,
}

let scope3: PossibleScopeType<typeof scope2> = scope
scope3 = scope

let scope4: PossibleScopeType<ScopeType<UserType, ScopeType<ModuleType>>> = scope
scope3 = scope

export type ParentScopeType<S> =
  // | S
  S extends ScopeType<infer X, infer Y> ? S : S

function test<T, S extends ScopeType<T>>(scope: S): void {
  let source: ParentScopeType<S> | undefined = scope
  console.log(source)
}

test<UserType, ScopeType<UserType>>(scope2)

In the function test at the bottom, this line throws an error at source:

let source: ParentScopeType<S> | undefined = scope

It says:

Type 'S' is not assignable to type 'ParentScopeType<S> | undefined'.
  Type 'ScopeType<T, unknown>' is not assignable to type 'ParentScopeType<S>'.(2322)

I have struggled with this for a few hours. Literally in this case I am just debugging, and in ParentScopeType I simply return S (ScopeType) in both conditional branches! If I comment out the conditional branching and just put the following, it compiles:

type ParentScopeType<S> = S

That is the same as doing:

function test<T, S extends ScopeType<T>>(scope: S): void {
  let source: S | undefined = scope
  console.log(source)
}

I don't see why the conditional branch changes the logic at all, because it is returning S in both branches... Something is fishy with this, my understanding of how conditional types work must be off. Can you show me how I can keep the function test as is, and get it to compile? The ParentScopeType should be a type of the current scope and all its parents, like Scope1 | Scope1Parent | Scope1ParentParent | ....

Here is another slight variation, same error.

An even more primitive example is:

type ParentType<S> = S extends object ? string : number

function test2<T>(scope: T): void {
  let source: ParentType<T> | undefined = 'hello'
  console.log(source)
}

I get the error:

Type 'string' is not assignable to type 'ParentType<T>'

Why is it not working like I'd expect? Shouldn't source: string | number in the last example?

It compiles like this though:

type ParentType<S> = S extends string ? string : unknown

function test2<T>(scope: T): void {
  let source: ParentType<T> | undefined = 'hello'
  console.log(source)
}

I don't understand what's going on.

CodePudding user response:

As I understand it, it has to do with the fact that conditional types become distributive when handed a union type (see the handbook here).

This fails:

type Generic<T> = T extends string ? T : T;

function test<T>(t: T): void {
  const x: Generic<T> = t; // Type 'T' is not assignable to type 'Generic<T>'.
}

To counter the distributive behavior, you can wrap the type parameter being checked in a tuple, so this works:

type Generic<T> = [T] extends [string] ? T : T;

function test<T>(t: T): void {
  const x: Generic<T> = t; // OK
}

For your example, if you change your ParentScopeType as below, you'll get rid of the compiler error:

export type ParentScopeType<S> = [S] extends [ScopeType<unknown>] ? S : S;
  • Related