Description
TypeScript Version: 3.6.0-dev.20190727
Search Terms: conditional, generic, inheritance, variance
Code
Prior to the --strictFunctionTypes compiler flag in the strict suite, this previously worked fine.
type GetPropertyNamesOfType<T, RestrictToType> = { [PropertyName in Extract<keyof T, string>]: [T[PropertyName]] extends [RestrictToType] ? PropertyName : never }[Extract<keyof T, string>];
type GetAllPropertiesOfType<T, RestrictToType> = Pick<T, GetPropertyNamesOfType<Required<T>, RestrictToType>>;
export interface IRequestA<T extends object>{
getValue(name: keyof GetAllPropertiesOfType<T, string>): string;
getObject<ObjectType extends object = never>(name: keyof GetAllPropertiesOfType<T, ObjectType>): IRequestA<ObjectType>;
}
interface Base {
BaseString: string;
BaseNumber: number;
BaseObject: Base;
}
interface Derived extends Base {
DerivedString: string;
DerivedNumber: number;
DerivedObject: Base;
}
function main(a: IRequestA<Derived>): void {
const a1: IRequestA<Base> = a;
a1.getValue("BaseString");
a1.getObject<Base>("BaseObject");
}
As the result of a suggestion in Issue 28671 the interface was switch to a type. This worked until TypeScript version 3.5 where the assignment again started to fail when inheritance was involved with T.
type GetPropertyNamesOfType<T, RestrictToType> = { [PropertyName in Extract<keyof T, string>]: [T[PropertyName]] extends [RestrictToType] ? PropertyName : never }[Extract<keyof T, string>];
type GetAllPropertiesOfType<T, RestrictToType> = Pick<T, GetPropertyNamesOfType<Required<T>, RestrictToType>>;
export type IRequestB<T extends object> = {
getValue(name: keyof GetAllPropertiesOfType<T, string>): string;
getObject<ObjectType extends object = never>(name: keyof GetAllPropertiesOfType<T, ObjectType>): IRequestB<ObjectType>;
}
interface Base {
BaseString: string;
BaseNumber: number;
BaseObject: Base;
}
interface Derived extends Base {
DerivedString: string;
DerivedNumber: number;
DerivedObject: Base;
}
function main(b: IRequestB<Derived>): void {
const b1: IRequestB<Base> = b;
b1.getValue("BaseString");
b1.getObject<Base>("BaseObject");
}
With some changes I got it to work by switching around the allowed property name list:
type GetPropertyNamesOfType<T, RestrictToType> = { [PropertyName in Extract<keyof T, string>]: [T[PropertyName]] extends [RestrictToType] ? PropertyName : never }[Extract<keyof T, string>];
type GetAllPropertiesOfType<T, RestrictToType> = Pick<T, GetPropertyNamesOfType<Required<T>, RestrictToType>>;
export type IRequestC<T extends object> = {
getValue<K extends keyof GetAllPropertiesOfType<T, string>>(name: K): string;
getObject<ObjectType extends object = never>(name: keyof GetAllPropertiesOfType<T, ObjectType>): IRequestC<ObjectType>;
}
interface Base {
BaseString: string;
BaseNumber: number;
BaseObject: Base;
}
interface Derived extends Base {
DerivedString: string;
DerivedNumber: number;
DerivedObject: Base;
}
function main(c: IRequestC<Derived>): void {
const c1: IRequestC<Base> = c;
c1.getValue("BaseString");
c1.getObject<Base>("BaseObject");
}
Expected behavior:
I would expect b to be assignable to b1 in the example above to be able to work with the subset of properties that are specific to the base type.
Actual behavior:
The assignment doesn't work or I need to jump through hoops to make it work.
I don't know if I'm just trying to limp by staying one step ahead of type checker improvements that will again cause me to make changes or possibly disallow me from accomplishing what I'm looking for.
I'm also not sure about the inconsistency in the need to update the getValue
method versus getObject
.
Playground Link:
Demo
Related Issues:
Issue 28671
Issue 24190