I've been using a Conditional Type in TypeScript to create a Recursive Partial. The issue I'm having is hard to explain without an example, so please see the code below (note: my particular use case is more complex, but the example illustrates the issue):
// The RecursivePartial type used in the illustration
type RecursivePartial<T> = {
[P in keyof T]?:
T[P] extends (infer U)[] ? RecursivePartial<U>[] :
T[P] extends object ? RecursivePartial<T[P]> :
T[P];
};
// A class we will use -- note it has a string property called "name"
// and a numeric property called "commonProperty"
export class Thingy1 {
public constructor(public name: string) {}
public commonProperty = 1;
}
// Another class we will use -- it has two properties that
// Thingy1 does not have (label and value) but it also has a
// numeric property called "commonProperty" just like Thingy1
export class Thingy2 {
public constructor(public label: string, public value: number) {}
public commonProperty = 2;
}
// A class with a property of type Thingy1
export class Thingy3 {
public constructor(public thingy1: Thingy1) {}
}
// Define a RecursivePartial of Thingy3
const partialOfThingy3: RecursivePartial<Thingy3> = {};
// The following line illustrates the problem! I set the Thingy1-type
// property to an instance of Thingy2, and TypeScript seems perfectly
// find with it!
// HOWEVER, if I change the commonProperty name (or remove it) from
// either the Thingy1 or Thingy2 class, TypeScript complains as expected
partialOfThingy3.thingy1 = new Thingy2("hi", 2); // This is allowed!
As noted in the comments, TypeScript allows us to set a property in the RecursivePartial to an object of a different type than the property in the original class. This oddity happens only when the "different type" shares at least one property with the original class.
Is there a way to fix this behavior so that the Conditional Type will not allow a property to be set to an object of an incorrect type just because that type happens to share a property with the correct type?
Simplification thanks to kikon:
The following simplifies the example down to the root of the issue:
type RecursivePartialThingy3 = {
thingy1?: {
commonProperty?: number;
name?: string;
}
}
const partialOfThingy3: RecursivePartialThingy3 = {};
const thingy2 = {commonProperty: 2, label: "hi"};
// This is allowed:
partialOfThingy3.thingy1 = thingy2;
// And yet, this is NOT allowed:
partialOfThingy3.thingy1 = {commonProperty: 2, label: "hi"};
In the second case, TypeScript complains that "Object literal may only specify known properties, and 'label' does not exist in type '{ commonProperty?: number | undefined; name?: string | undefined; }'"
So why do we have two different requirements for type checking in these two cases? Is it possible to tell TypeScript to issue a warning or error in the first case as it does in the second case?
Thanks!