Skip to content

Design Meeting Notes, 10/28/2016 #11943

Closed
@mhegazy

Description

@mhegazy

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design NotesNotes from our design meetings

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions