Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inheritance with generics disappears when using typings and "this" type #54398

Closed
alberto-schena-gizero opened this issue May 25, 2023 · 2 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@alberto-schena-gizero
Copy link

Bug Report

🔎 Search Terms

type generics constraints incompatible assignable typings extends class subclass this

🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about any of these keywords "type generics constraints incompatible assignable typings extends class subclass this".

⏯ Playground Link

Playground link with relevant code

💻 Code

// Original classes

export class Subject<TSubscriber, TMethod extends TSubscriber[keyof TSubscriber]>
{
    constructor(protected readonly selector: (subscriber: TSubscriber) => TMethod)
    {}
}

export class Behaviors<TEntity extends Entity>
{
    protected readonly _onAdded$ = new Subject((it: OnBehaviorAdded<TEntity>) => it.onBehaviorAdded);

    constructor(protected readonly entity: TEntity)
    {}
}

export interface OnBehaviorAdded<TEntity extends Entity>
{
    onBehaviorAdded(entity: TEntity): void;
}

export abstract class Entity
{
    readonly behaviors = new Behaviors(this);
}

export class Device
    extends Entity
{
    readonly id = 0;
}

// Generated typings (renamed)

export declare class Subject_d_ts<TSubscriber, TMethod extends TSubscriber[keyof TSubscriber]> {
    protected readonly selector: (subscriber: TSubscriber) => TMethod;
    constructor(selector: (subscriber: TSubscriber) => TMethod);
}
export declare class Behaviors_d_ts<TEntity extends Entity_d_ts> {
    protected readonly entity: TEntity;
    protected readonly _onAdded$: Subject_d_ts<OnBehaviorAdded_d_ts<TEntity>, (entity: TEntity) => void>;
    constructor(entity: TEntity);
}
export interface OnBehaviorAdded_d_ts<TEntity extends Entity_d_ts> {
    onBehaviorAdded(entity: TEntity): void;
}
export declare abstract class Entity_d_ts {
    readonly behaviors: Behaviors_d_ts<this>;
}
export declare class Device_d_ts extends Entity_d_ts {
    readonly id = 0;
}

// Checks

type fromtyping = Device_d_ts extends Entity_d_ts ? 'ok' : 'ko';
type fromsources = Device extends Entity ? 'ok' : 'ko';

class Foo extends Behaviors<Device>{}
class Bar extends Behaviors_d_ts<Device_d_ts>{}

🙁 Actual behavior

As you can see, Device_d_ts does not extend Entity_d_ts (fromtyping is 'ko'), whereas Device extends Entity (fromsources is 'ok').
Thus, through typings the type check yields a different result from the original type definitions.

🙂 Expected behavior

I would have expected the same behavior regardless of using typings or not.

Which one is correct?

  • Both fromtyping and fromsources must be 'ko'
  • Both fromtyping and fromsources must be 'ok'
@fatcerberus
Copy link

fatcerberus commented May 25, 2023

My initial guess is some kind of discrepancy in variance measurement but yeah, this is weird since Device_d_ts is explicitly a subclass of Entity_d_ts and there’s no error on the class definition that would suggest the subclassing is invalid

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label May 26, 2023
@RyanCavanaugh
Copy link
Member

This example encounters a limitation in our variance measurement algorithm. Annotated example:

export class Subject<in TSubscriber, out TMethod extends TSubscriber[keyof TSubscriber]>
{
    constructor(protected readonly selector: (subscriber: TSubscriber) => TMethod)
    {}
}

export class Behaviors<out TEntity extends Entity>
// Under v1, this is incorrectly measured as covariant on TEntity
// Under v2, it is correctly measured as invariant
{
    // v1
    protected readonly _onAdded$ = new Subject((it: OnBehaviorAdded<TEntity>) => it.onBehaviorAdded);
    // v2
    // protected readonly _onAdded$: Subject<OnBehaviorAdded<TEntity>, (entity: TEntity) => void> = null as any;

    constructor(protected readonly entity: TEntity)
    {}
}

export interface OnBehaviorAdded<in TEntity extends Entity>
{
    onBehaviorAdded(entity: TEntity): void;
}

export abstract class Entity
{
    readonly behaviors = new Behaviors(this);
}

export class Device
    extends Entity
{
    readonly id = 0;
}

See #44572 for more details (including links to longer discussion)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

3 participants