Repository navigation
Suggestion: Type Property type #1295
Description
Motivations
A lot of JavaScript library/framework/pattern involve computation based on the property name of an object. For example Backbone model, functional transformation pluck, ImmutableJS are all based on such mechanism.
//backbone
var Contact = Backbone.Model.extend({})
var contact = new Contact();
contact.get('name');
contact.set('age', 21);
// ImmutableJS
var map = Immutable.Map({ name: 'François', age: 20 });
map = map.set('age', 21);
map.get('age'); // 21
//pluck
var arr = [{ name: 'François' }, { name: 'Fabien' }];
_.pluck(arr, 'name') // ['François', 'Fabien'];We can easily understand in those examples the relation between the api and the underlying type constraint.
In the case of the backbone model, it is just a kind of proxy for an object of type :
interface Contact {
name: string;
age: number;
}For the case of pluck, it's a transformation
T[] => U[]
where U is the type of a property of T prop.
However we have no way to express such relation in TypeScript, and ends up with dynamic type.
Proposed solution
The proposed solution is to introduce a new syntax for type T[prop] where prop is an argument of the function using such type as return value or type parameter.
With this new type syntax we could write the following definition :
declare module Backbone {
class Model<T> {
get(prop: string): T[prop];
set(prop: string, value: T[prop]): void;
}
}
declare module ImmutableJS {
class Map<T> {
get(prop: string): T[prop];
set(prop: string, value: T[prop]): Map<T>;
}
}
declare function pluck<T>(arr: T[], prop: string): Array<T[prop]> // or T[prop][] This way, when we use our Backbone model, TypeScript could correctly type-check the get and set call.
interface Contact {
name: string;
age: number;
}
var contact: Backbone.Model<Contact>;
var age = contact.get('age');
contact.set('name', 3) /// errorThe prop constant
Constraint
Obviously the constant must be of a type that can be used as index type (string, number, Symbol).
Case of indexable
Let's give a look at our Map definition:
declare module ImmutableJS {
class Map<T> {
get(prop: string): T[string];
set(prop: string, value: T[string]): Map<T>;
}
}If T is indexable, our map inherit of this behavior:
var map = new ImmutableJS.Map<{ [index: string]: number}>;Now get has for type get(prop: string): number.
Interrogation
Now There is some cases where I have pain to think of a correct behavior, let's start again with our Map definition.
If instead of passing { [index: string]: number } as type parameter we would have given
{ [index: number]: number } should the compiler raise an error ?
if we use pluck with a dynamic expression for prop instead of a constant :
var contactArray: Contact[] = []
function pluckContactArray(prop: string) {
return _.pluck(myArray, prop);
}or with a constant that is not a property of the type passed as parameter.
should the call to pluck raise an error since the compiler cannot infer the type T[prop], shoud T[prop] be resolved to {} or any, if so should the compiler with --noImplicitAny raise an error ?
Activity
NoelAbrahams commentedon Nov 28, 2014
Possible duplicate of #394
See also #1003 (comment)
fdecampredon commentedon Nov 28, 2014
Noel Abrahams (@NoelAbrahams) I really don't think that it's a duplicate of #394, on the contrary both features are pretty complementary something like :
Would be ideal
s-panferov commentedon Nov 29, 2014
François de Campredon (@fdecampredon)
What to do in this case?
fdecampredon commentedon Nov 29, 2014
It's more or less the same case than the one from the last paragraph of my issue. Like I said we have multiple choice, We can report an error, or infer
anyforT[prop], I think the second solution is more logicalIgorbek commentedon Dec 1, 2014
Great proposal. Agree, it would be useful feature.
NoelAbrahams commentedon Dec 1, 2014
François de Campredon (@fdecampredon), I do believe this is a duplicate. See the comment from Dan and the corresponding response which contains the suggestion for
membertypeof.IMO all this is a lot of new syntax for a rather narrow use-case.
Igorbek commentedon Dec 1, 2014
Noel Abrahams (@NoelAbrahams) it's not the same.
memberof Treturns type which instance could only be a string with valid property name ofTinstance.T[prop]returns type of the property ofTnamed with string which is represented bypropargument/variable.There's a brifge to
memberofthat type ofpropparameter should bememberof T.Actually, I would like to have more rich system for type inference based on type metadata. But such operator is a good start as well as
memberof.RyanCavanaugh commentedon Dec 1, 2014
This is interesting and desirable. TypeScript doesn't do well with string-heavy frameworks yet and this would obviously help a lot.
NoelAbrahams commentedon Dec 1, 2014
True. Still doesn't change the fact that this is a duplicate suggestion.
Not sure about that. Seems rather piecemeal and somewhat specific to the proxy-object pattern outlined above. I would much prefer a more holistic approach to the magic string problem along the lines of #1003.
spion commentedon Dec 2, 2014
#1003 suggests
anyas the return type of a getter. This proposal adds on top that by adding a way to look up the value type too - the mix would result in something like this:NoelAbrahams commentedon Dec 2, 2014
spion (@spion), did you mean #394? If you were to read down further you would see the following:
fdecampredon commentedon Dec 2, 2014
OK Noel Abrahams (@NoelAbrahams) there was a comment in #394 that was trying to describe more or less the same thing that this one.
Now I think than
T[prop]is perhaps a little more elegant than the different propositions of this comment, and that the proposition in this issue goes perhaps a little further in the reflection.For theses reason I don't think that it should be closed as a duplicate.
But I guess I'm biased since I'm the one who wrote the issue ;).
NoelAbrahams commentedon Dec 2, 2014
François de Campredon (@fdecampredon), more the merrier 😃
61 remaining items
tinganho commentedon Aug 30, 2016
Daniel Rosenwasser (@DanielRosenwasser) Having barely went down the rabbit hole. I still can't find any problems with implementing Kagami Sascha Rosylight (@saschanaz) idea? I think
keysofis redundant in this case.T[p]already relates thatpmust be one of the literal props ofT.My rough implementation thought was to introduce a new type called
PropertyReferencedType.When entering a function declared with a return type that is of
PropertyReferencedTypeor entering a function that referencesPropertyReferencedType: A type of aElementAccessExpressionwill be augmented with a property that references the symbol of the accessed property.So a type with a referenced property symbol is assignable to a
PropertyReferencedType. During checking, thereferencedPropertymust correspond topinT[p]. Also the parent type of a element access expression must be assignable toT. And to make things easierpmust also be const.The new type
PropertyReferencedTypeonly exists inside the function as an "unresolved type". On call site one have to resolve the type withp:A
PropertyReferencedTypeonly propagates through function assignments and cannot propagate through call expressions, because aPropertyReferencedTypeis only a temporary type meant to help with checking the body of a function with return typeT[p].Igorbek commentedon Sep 2, 2016
If you introduce
keysofandT[K]type operators, would it mean we could use them like this:Daniel Rosenwasser (@DanielRosenwasser) wouldn't your example have the same meaning with my
rtm commentedon Sep 12, 2016
I am not seeing how the signature would be written for Underscore's
_.pick:tinganho commentedon Sep 12, 2016
Bob Myers (@rtm) I suggested it in #1295 (comment). Though it might be better to open a new issue, even though it is related to this one.
ahejlsberg commentedon Nov 2, 2016
Implementation now available in #11929.