Skip to content

Interfaces and types that use conditional types and inheritance no longer assignable #32608

Closed
@mtreder

Description

@mtreder

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

Metadata

Metadata

Assignees

Labels

Design LimitationConstraints of the existing architecture prevent this from being fixed

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions