Typescript: Recursive type definitions does not seem to work handle generics?

Created on 15 Aug 2017  路  8Comments  路  Source: microsoft/TypeScript


TypeScript Version: 2.4.0

Code

// test.ts

type RecursivePartial<T> = {
    [P in keyof T]?: RecursivePartial<T[P]>;
};

type State<T> = { value: T };

function create_1<T>(){

    let _x: RecursivePartial<State<T>>;
    let _y: State<RecursivePartial<T>>;

    _x = _y;
}

function create_2<T>(){
 /*

 */

    let x: RecursivePartial<State<T>>;
    let y: State<T>;

    /*
        Type 'State<T>' is not assignable to type RecursivePartial<State<T>>'.
            Types of property 'value' are incompatible.
                Type 'T' is not assignable to type RecursivePartial<T>[P]>'.
                    Type 'T[string]' is not assignable to type 'RecursivePartial<T[P]>'.
    */

    x = y; 
}

Expected behavior:
I had expected the second example to be valid typescript, i.e. State<T> should be assignable to RecursivePartial<State<T>>. This should be the case as any State<T> would be a partial of it self given T is the same type.

Actual behavior:
I get a type error (see above), it seems that the recursive type definition breaks when it encounters a generic?

Design Limitation

All 8 comments

Can't reproduce in 2.5.0-dev.20170627, maybe it's fixed?

The issue remain the same using typescript@rc.

npm install -g typescript@rc
tsc --version // 2.5.0
tsc test.ts

test.ts(30,5): error TS2322: Type 'State' is not assignable to type 'RecursivePartial>'.
Types of property 'value' are incompatible.
Type 'T' is not assignable to type 'RecursivePartial'.
Type 'T[P]' is not assignable to type 'RecursivePartial Type 'T[string]' is not assignable to type 'RecursivePartial

Should I try a different version than the newest release candidate?

Okay, weird. I couldn't reproduce because I'm using strictNullChecks. With strictNullChecks on, you get a different error (variable is used before being assigned) which goes away if you assign anything to _y and y:

type RecursivePartial<T> = {
    [P in keyof T]?: RecursivePartial<T[P]>;
};
type State<T> = { value: T };
function create_1<T>(){
    let _x: RecursivePartial<State<T>>;
    let _y: State<RecursivePartial<T>> = { } as any; // anything
    _x = _y;
}
function create_2<T>(){
    let x: RecursivePartial<State<T>>;
    let y: State<T> = {} as any; // anything
    x = y;  // no error
}

With strictNullChecks off, I get the error you're reporting. Sorry about the TS version red herring.

I confirm, with strictNullChecks the problem seem to go away completely. Which is really weird. @jcalz would you mind posting your other thoughts in here too? I'll just do it otherwise. ^_^

I'm not sure the other workarounds from my SO answer are relevant in a bug report, but to summarize:

  • you can do type assertions to force the assignment to work,

    • or you could define RecursivePartial like this: type RecursivePartial<T> = {[P in keyof T]?: T[P] | RecursivePartial<T[P]>}; or possibly: type RecursivePartial<T> = T | {[P in keyof T]?: RecursivePartial<T[P]>}; to help the compiler accept that a T value can be assigned to a RecursivePartial<T> variable.

The question for this issue: when strictNullChecks is off, is this behavior a bug, a design limitation, or the intended behavior? Someone else probably needs to weigh in on that.

I believe I'm having a similar issue with a recursive partial type DeepPartial from typeorm/typeorm. Here's a simple example:

// typeorm/typeorm DeepPartial type
declare type DeepPartial<T> = {
  [P in keyof T]?: DeepPartial<T[P]>;
};

interface ITestInterface {
  id: number;
}

class TestClass<T extends ITestInterface> {
  test(full: T, partial: Partial<T>, deepPartial: DeepPartial<T>) {

    full.id = 1;

    partial.id = 1;

    // type '1' is not assignable to type 'DeepPartial<T["id"]>'
    deepPartial.id = 1;
  }
}

I have also run into this issue with TypeORM. I've been poking at it for a while trying to figure out what's going on. Here's a contrived example I've been using to explore the problem and possible workarounds:

// As defined in TypeORM
type DeepPartial<T> = {
  [P in keyof T]?: DeepPartial<T[P]>;
};

export class Content {
  id: number
  createdAt: Date
  createdById?: number;
}

export class Blog extends Content {
  title: string
}

// This is what I want to be able to do but it does not compile in 2.8.1 or 2.9 dev. The error produced is: 
// Type 'Date' is not assignable to type 'DeepPartial<C["createdAt"]> | undefined'. Type 'Date' is not assignable to type 'DeepPartial<C["createdAt"]>'.
function props<C extends Content>(): DeepPartial<C> {
  let data = {} as DeepPartial<C>
  data.createdAt = new Date()
  data.createdById = 42
  return data
}

// This is almost a workaround. I'm not sure exactly why this works but it does compile in 2.9 dev.
// It does not compile in 2.8.1, producing error:
// TS2352: Type 'DeepPartial<Content>' cannot be converted to type 'DeepPartial<C>'.
function props<C extends Content>(): DeepPartial<C> {
  let data = {} as DeepPartial<Content>
  data.createdAt = new Date()
  data.createdById = 42
  return data as DeepPartial<C>
}

let content: DeepPartial<Blog> = props<Blog>()

Self-referential type aliases aren't yet fully supported.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sandersn picture sandersn  路  265Comments

fdecampredon picture fdecampredon  路  358Comments

nitzantomer picture nitzantomer  路  135Comments

rbuckton picture rbuckton  路  139Comments

xealot picture xealot  路  150Comments