Design Meeting Notes, 10/28/2016
mhegazy opened this issue · 1 comments
mhegazy commented
keyof T and T[K]
Note about terminology
keyofis a type operator. Note, name change from #10425 tokeyof; since the type name represents one value and not the whole value domain. this is inline with the other names of types e.g. we callnumberand notnumbers,- Henceforth, formal names of these type operators:
keyof T=> Index Type QueryT[K]=> Indexed Access Type
Details
keyof T
-
known properties and index signatures (
number | stringfor string indexers andnumberfor numeric indexers) -
Examples:
keyof { a: number, b:string}=>"a" | "b"keyof { [x: string] : number }=>string | numberkeyof { [x: number] : number }=>numberkeyof {}[]=>"toString" | "pop" |...| numberkeyof { a: number, b:string } | { a: number, c:string}=>"a"keyof { a: number, b:string } & { a: number, c:string}=>"a" | "b" | "c"
-
What about symbols?
- symbols not supported yet. we need to make symbols literal types first.
-
privateandprotected
A piece of trivia, accessing private and protected properties are allowed for index access todayclass C { private x; } let c: C; c.x; // not allowed c["x"]; // is allowed today
- should this apply to the new type operator:
ts type T = C["x"]; // OK or not? - for
nameofin C# does not do this either- it exists at run time
- against
- we should not carry past misdeeds in new features
- it exposes implementation details
- not valid for declaration emit, since we strip out the type
- Decision
keyof Tis public-only known properties, private and protected properties are not included
- should this apply to the new type operator:
T[K]
- only index with a type
Kparameter iff it is constraint to thekeyof T - this grantees that the output is type of a property
- can be used with literal types
class Thing {
name: string;
width: number;
}
type q1 = Thing["name"]; // string
type q2 = Thing["name" | "width"]; //string | number
type q3= Thing["name" | "foo"]; // Error "foo" is not a property on ThingPutting them together
function getProperty<T, K extends keyof T>(obj: T, key:K) {
return obj[key]; // T[K]
}
function setProperty<T, K extends keyof T>(obj: T, key:K, value: T[K]) {
obj[K] = value;
}
var thing: Thing;
var thingList: Thing[];
getPropeprty(thing, "foo") // Error
getPropeprty(thing, "name") // string
getPropeprty(thingList, 1) // Thing
getPropeprty(thingList, 1 + 2) // Thing
setPropoerty(thing, "name", "my name"); // OK
setPropoerty(thing, "name", 3); // ErrorIndex access unification
- The way we do
T[K]for a type should be the same as that for an expressiont[k]. - so
thing[condition ? "name" : "width"]; // should be string | numberIssues
Variance
Consider this:
setPropoerty(thing, condition? "name" : "width", "string"); - this is allowed today, but it is unsafe...
Keyof Tshould be a union for types of all properties for read, but an intersection for write. - This is the same behavior we have today for indexers anyways:
interface I {
a: number;
b: string;
[x: string] : number | string;
}
const k = condition? "a" : "b";
x[k] // number | string;
x[k] = "bar"; // allowed but not safe- We are getting close to the "safe variance" glass ceiling
- solving these all would need to add variance in the type system
- a readonly, writeonly, and readwrite types
- this applies to class properties, parameters, everything
- this would be a large braking change, and have to be done holistically
- the safer it would be the tighter it would be for users to use
Where we can land:
- For expressions, it would be an error i.e.
thing[condition ? "name" : "width"] = "bar"; /// Error, we know unsafe - but not for generic type argument
setProperty(thing, condition ? "name" : "width", "bar"); // OK Note about readonly:
- you can write a readonly propoerty using
setPropoerty. - there is not much we can do here. the time where this would be observed is at instantiation time, which is too late at that point to report errors.
- and again it goes back to variance, you need
readonly keyof Tandreadwrite keyof T.
aluanhaddad commented
Absolutely awesome. This adds a whole new dimension of expressiveness to the language and does so in a way that is just brilliantly JavaScript.
keyof {}[] => "toString" | "pop" |...| number
is just beautiful.