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?
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 Should I try a different version than the newest release candidate?
Types of property 'value' are incompatible.
Type 'T' is not assignable to type 'RecursivePartial
Type 'T[P]' is not assignable to type 'RecursivePartial
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:
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.