Skip to content
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

Design Meeting Notes, 10/28/2016 #11943

Closed
mhegazy opened this issue Oct 30, 2016 · 1 comment
Closed

Design Meeting Notes, 10/28/2016 #11943

mhegazy opened this issue Oct 30, 2016 · 1 comment
Labels
Design Notes Notes from our design meetings

Comments

@mhegazy
Copy link
Contributor

mhegazy commented Oct 30, 2016

keyof T and T[K]

Note about terminology

  • keyof is a type operator. Note, name change from Keysof type operator #10425 to keyof; 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 call number and not numbers,
  • Henceforth, formal names of these type operators:
    • keyof T => Index Type Query
    • T[K] => Indexed Access Type

Details

keyof T

  • known properties and index signatures (number | string for string indexers and number for numeric indexers)

  • Examples:

    • keyof { a: number, b:string} => "a" | "b"
    • keyof { [x: string] : number } => string | number
    • keyof { [x: number] : number } => number
    • keyof {}[] => "toString" | "pop" |...| number
    • keyof { 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.
  • private and protected
    A piece of trivia, accessing private and protected properties are allowed for index access today

    class 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
      • nameof in 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 T is public-only known properties, private and protected properties are not included

T[K]

  • only index with a type K parameter iff it is constraint to the keyof 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 Thing

Putting 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); // Error

Index access unification

  • The way we do T[K] for a type should be the same as that for an expression t[k].
  • so
thing[condition ? "name" : "width"]; // should be string | number

Issues

Variance

Consider this:

setPropoerty(thing, condition? "name" : "width", "string");  
  • this is allowed today, but it is unsafe... Keyof T should 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 T and readwrite keyof T.
@mhegazy mhegazy added the Design Notes Notes from our design meetings label Oct 30, 2016
@mhegazy mhegazy closed this as completed Oct 30, 2016
@aluanhaddad
Copy link
Contributor

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Design Notes Notes from our design meetings
Projects
None yet
Development

No branches or pull requests

2 participants