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'.
Could you elaborate on why using Action instead of T isn't sufficient in this case?
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...
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
});
@DanielRosenwasser Over to you - we agreed that this should be allowed in the comparability relation assuming it can be reasonably spec'd.
Most helpful comment
@DanielRosenwasser Over to you - we agreed that this should be allowed in the comparability relation assuming it can be reasonably spec'd.