Skip to content

Allow type assertions from T-constrained type variable to subtypes of T #20821

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

Closed
ghoullier opened this issue Dec 20, 2017 · 4 comments
Closed
Labels
Experience Enhancement Noncontroversial enhancements Help Wanted You can do this Suggestion An idea for TypeScript
Milestone

Comments

@ghoullier
Copy link

TypeScript Version: 2.6.2

Code

enum ActionType {
    Call
}

interface Action {
    type: ActionType;
}

interface CallAction extends Action {
    context: any;
}

class Service {
    exec<T extends Action>(action: T) {
        if (action.type == ActionType.Call) {
            (<CallAction>action).context = {};
        }
    }
}

Expected behavior:

I should use cast because CallAction extends Action

Actual behavior:

I have the following compilation error:

(<CallAction>action).context = {};
Type 'T' cannot be converted to type 'CallAction'.
Type 'T' cannot be converted to type 'CallAction'.
  Type 'Action' is not comparable to type 'CallAction'.
    Property 'context' is missing in type 'Action'.
@DanielRosenwasser
Copy link
Member

Could you elaborate on why using Action instead of T isn't sufficient in this case?

@DanielRosenwasser DanielRosenwasser added In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Dec 20, 2017
@ghoullier
Copy link
Author

I have an other class Store

class Store {
    db = new Map<ActionType, Action[]>();
    service = new Service();
    store(action: Action) {
        if (this.db.has(action.type)) {
            this.db.set(action.type, []);
        }
        this.db.get(action.type).push(action);
        this.service.exec(action);
    }
    storeGeneric<T extends Action>(action: T) {
        if (this.db.has(action.type)) {
            this.db.set(action.type, []);
        }
        this.db.get(action.type).push(action);
        this.service.exec(action);
    }
}
const store = new Store();

This example doesn't work

store.store({
    type: ActionType.Call,
    context: {}
});
Argument of type '{ type: ActionType; context: {}; }' is not assignable to parameter of type 'Action'.
  Object literal may only specify known properties, and 'context' does not exist in type 'Action'.

This example works

store.storeGeneric<CallAction>({
    type: ActionType.Call,
    context: {}
});

That's why I use T instead Action in my Service. I see there is no problem when I switch to a non Generic method. But I want to know why it doesn't work, it's a non intuitive behavior...

@ghoullier
Copy link
Author

I solved the problem using generic class instead of generic methods

interface Typed<U> {
    type: U;
}

interface Service<T extends Typed<U>, U> {
    exec(action: T): T;
}

class Store<T extends Typed<U>, U> {
    db = new Map<U, T[]>();
    constructor(private service: Service<T, U>) {}
    store(action: T): T {
        if (this.db.has(action.type)) {
            this.db.set(action.type, []);
        }
        this.db.get(action.type).push(action);
        this.service.exec(action);
        return action;
    }
}


enum ActionType {
    Call
}

interface Action extends Typed<ActionType>{
    type: ActionType;
}

interface CallAction extends Action {
    context: any;
}

class ActionService implements Service<Action, ActionType> {
    exec(action: Action) {
        if (action.type === ActionType.Call) {
            console.log('ActionType.Call', (<CallAction>action).context);
        }
        return action;
    }
}
const service = new ActionService();
const store = new Store<Action, ActionType>(service);
store.store({
    type: ActionType.Call
});

@RyanCavanaugh RyanCavanaugh added Committed The team has roadmapped this issue and removed In Discussion Not yet reached consensus labels Jan 30, 2018
@RyanCavanaugh
Copy link
Member

@DanielRosenwasser Over to you - we agreed that this should be allowed in the comparability relation assuming it can be reasonably spec'd.

@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Jun 23, 2021
@RyanCavanaugh RyanCavanaugh added the Help Wanted You can do this label Jun 23, 2021
@RyanCavanaugh RyanCavanaugh changed the title Cast problem with generics method definition Allow type assertions from T-constrained type variable to subtypes of T Jun 23, 2021
@RyanCavanaugh RyanCavanaugh added Experience Enhancement Noncontroversial enhancements and removed Committed The team has roadmapped this issue labels Jun 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Experience Enhancement Noncontroversial enhancements Help Wanted You can do this Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants