-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Omit index signature with generic type constraints? #10941
Comments
The problem with not having the index signature is that your class becomes unsound to basic subtyping. Consider: interface Stuff {
age: number;
}
interface StuffWithName extends Stuff {
name: string;
}
interface Constraint<T> {
// [key: string]: T;
}
class Base<T extends Constraint<T>> {
setT(x: T) { }
}
class Derived extends Base<Stuff> {
}
let d = new Derived();
let s: StuffWithName = { age: 10, name: '' };
d.setT(s); |
Well, I definitely want to keep the index signature around, but at the same time I don't want every derived type to be forced to implement it. Like something that's defined somewhere up in the hierarchy of types that's properly restricting any derived type without having them carrying more information than they should. I don't know exactly how the type system works internally, but I'm pretty sure there's no such thing as an "inheritance chain for types", am I right? Coming back to the realm of possibilities, how would you address my problem then? Am I really limited to one of the two propositions I made (no index signature or index signature everywhere)? |
You could write interface MyType extends Constraint {
prop: number;
label: number;
code: number
} to save the trouble of typing the index signature. Thoughts? |
Typing the index signature isn't a problem at all, my problem is more about the fact my derived type carries it. Maybe I should tell you more about what I am trying to achieve: My base class is a React component that's supposed to take some kind of store as parameter, wrap it up in some viewmodel logic, and make it up for grabs for a derived class to use it. It looks like this interface StoreItem<T> {
metadata: {},
value: T
}
interface Store {
[key: string]: StoreItem<any>
}
abstract class Base<P, E extends Store> extends React.Component<P, void> {
viewModel: E;
constructor(props, store: E) {
this.viewmodel = createViewModel(store):
}
renderProp(prop: StoreItem<any>) {
// Something something rendering
}
}
interface MyStore {
prop1: StoreItem<string>;
prop2: StoreItem<number>;
}
class MyClass extends Base<{}, MyStore> {
constructor(props) { super(props, myStore); }
render() {
const {prop1, prop2} = this.viewModel;
return <div>{this.renderProp(prop1)}{this.renderProp(prop2)}</div>;
}
} That's the essence of what I'm trying to do. This doesn't work because All those stores are generated from my server-side model, and I want to be able to safely modify this model, meaning if that one property is removed/changes, my app would stop compiling. Let's say Removing the index signature altogether could make this work, but at the expense of losing all typechecking in the base class and making the contract of the class implicit (how am I suppose to enforce that the type parameter should follow an index signature that's not there?). |
This has been a problem for me as well. I think there needs to be a way to differentiate "handles objects with any key as long as the value has type T" and "requires object that can handle any key as long as the type is T" |
I had a similar problem and here is how I "solved" it:
I also have a generic class that takes above types:
Now, here is how I did it:
However, I defined an interface whose sole purpose is to break compilation if some of X,Y,... does not conform to StringOnlyProps:
I even changed the generic above to require presence of such a constraint:
Now, if you change a type - |
@RyanCavanaugh did you have a chance to think about the issue? |
@JabX have you found a way around the issue, other than what @rado-nikolov suggested? |
@RyanCavanaugh @JabX const func = <T extends { [index: string]: string }>(a: T) => a
func<{ test: string }>({ test: '123' }) // works
interface A {
test: string
}
func<A>({ test: '123' }) // fails Playground |
Found a workaround to have an index signature constraint for function parameters, but not generic itself interface A {
a: string,
b: string
}
type AConstraint<T extends A> = T & { [index: string]: string }
const a = <T extends A>(props: AConstraint<T>): T => props
const propsTest1: A = { a: '1', b: '2' }
a(propsTest1 as AConstraint<A>)
const propsTest2 = { a: '1', b: '2', c: 'ftdrff' }
a(propsTest2) |
@Andy-MS sorry for disturbing you directly, but could someone from TypeScript team take a look at the issue? It was marked as 'Needs investigation' almost one and a half years ago. Thank you! |
@keenondrums I ran into the same issue with you and after reading this thread over and over + many trials and errors, here is a working solution. Thanks for starting the discussion. type ValueIsNumber<T> = {
[key in keyof T]: number;
}
class Base<T extends ValueIsNumber<T>> {
entity: T
}
interface MyType {
prop: number;
label: number;
code: number
}
class MyClass extends Base<MyType> {
// works!
} |
@kristw thank you! Yeah, using mapped types solves the issue, until I stumble upon a case when it doesn't :) |
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
TypeScript Version: nightly 2.1-20160914
I have a base class that takes a generic parameter with a constraint that is an index signature. Something like
This class is meant to be inherited from and provided with another type that satisfies the index signature but doesn't carry it itself. Something like
I don't want to put the index signature on MyType because destructuring
entity
won't output an error when the property doesn't exist:which is a critical feature for me.
I don't want to remove the index signature from the constraint because the base class completely revolves around it and I would lose a lot of type safety.
Is there something I missed? Should we have some new language feature to solve this issue?
(Now that I think of it I suppose it's a generic assignability issue between types, not restricted of type constraints, and it's not solved by #7029 since it's only about actual litterals, not types)
The text was updated successfully, but these errors were encountered: