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, 8/26/2016 #10566

Closed
DanielRosenwasser opened this issue Aug 26, 2016 · 7 comments
Closed

Design Meeting Notes, 8/26/2016 #10566

DanielRosenwasser opened this issue Aug 26, 2016 · 7 comments
Labels
Design Notes Notes from our design meetings

Comments

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Aug 26, 2016

Expanded Inference (Open & Expando Style Types)

There's lots of code out there that has no type annotations at all, no initializers, starts out as an empty object.
That's mostly existing JavaScript code.
Anders has been working on using CFA to track types completely in certain situations.

function foo() {
    let x;
    if (!!true) {
        x = 1;
    }
    else {
        x = "hello";
    }
    x; // has type 'number | string'
}

This works pretty well if we have an accurate picture of the control flow graph; however, you can't model this that well if these variables are captured in another function.
For instance:

function foo() {
    let x;
    if (!!true) {
        x = 1;
    }
    else {
        x = "hello";
    }
    x; // has type 'number | string'

    function captor() {
        x; // implicitly has type 'any'.
    }
}

That means that 'any' has a much more limited scope of being implicitly introduced.

This makes Salsa way more powerful!

Expando Objects

Expando is a (somewhat documented) feature and pattern in JS where you can continue tacking properties on to objects after their creation.

In certain sites, we can decide that an object is meant to be created as an "expando" type.
For instance, how useful is the type {} on its own?
The answer is "not very useful" (unless you're just using it for object identity), so we can build up the type as you assign properties to that object.

let x = {};      // after this, 'x' has type '{}'
x.a = 1;         // after this, 'x' has type '{ a: number }'
x.b = {};        // after this, 'x' has type '{ a: number; b: {} }'
x.b.y = "hello"; // after this, 'x' has type '{ a: number; b: { y: string} }'
x;               // 'x' has type '{ a: number; b: { y: string} }' here.

Every occurrence of x above has its own type.

Side note: This is widely used and the VS JS language service lost out a lot on this in switching to Salsa.
Both TypeScript and the Salsa JS LS would get this for free.

Q: Does this work for element access expressions?
A: No, haven't worked that far yet.

Q: Could we do this sort of thing for this in constructors?
A: Potentially very bad experience - what happens if users set a value to undefined? It's worth experimenting with though.

Q: Does this work for aliasing?
A: No, alias tracking would get pretty messy.

Expanded Array Element Inference

What about arrays?

This hasn't been implemented yet, but we have some ideas here.
Look for locations in which the array is added to (indexing, push, unshift).

let x = [];
x[0] = 4;        // 'x' has type 'number[]'
x.push("hello"); // 'x' has type '(number | string)[]'

Changes of Inference

General discussion about why an initializer is different from a deferred initialization.

Questions about inferring literal types, concerns about breaking changes.

tsconfig.json Inheritance

  • "Is there a reason why we wouldn't?"
  • "No."
  • "Then why don't we do it?"
  • Open questions (duh!).

Merge or Overwrite?

Overwrite.

Multiple or Single Inheritance

Everyone: "SINGLE."

That was easy.

Object Literal Rest & Spread

let x = { a: 0, b: "hi" };
let y = { a: true};

// This is equivalent to 'Object.assign({}, x, {a: 2 }, y)'
let z = {
    ...x,
    a: 2,
    ...y
};

// Ideally, has type '{ a: boolean, b: string }'
z;

Problem: Object.assign gives an intersection of its types.

Easy to do when types aren't generic.
But what if they are?

// What is the return type here?
function merge<T, U>(o: T, ext: U) {
    return {...o, ...ext};
}

Idea: new type operator like {...T, ...U}

Q: What about if U has an optional property that exists in T?
A: Probably union the types of each property.

Enumerable & Own Properties

class C {
    a = 0;
    b = "stuff";
    method() {
    }
}

let c = new C();
let d = { ...c };

Problem is that d doesn't get method because it's not enumerable or an own-property.
We'd need a new modifier in type members.

"Optionalization"

function setState<T>(o: T, ext: optional T) {
    return {...o, ...ext};
}

There are often scenarios where you want to create a type with optional portions.
Not a lot of time to discuss this today.

@MarkPieszak
Copy link

@DanielRosenwasser Hey Dan, the CFA to track types, is that something that could get us closer to the error tracking that for instance Flow has, in the classic example:

function foo(x) {
  return x * 10;
}
foo('Hello, world!');  // error
// although x is type any, it essentially must be Number in this case.

Just wondering! Would be an amazing addition to TS if so 👍

@DanielRosenwasser
Copy link
Member Author

Hey @MarkPieszak, that's a different thing actually. What's happening there is parameter type inference based on call-sites (note it doesn't work across modules). In general, it can be useful but it effectively makes such functions generic, which can impose a perf penalty. We've definitely considered it though.

@MarkPieszak
Copy link

Yeah I imagine all those checks constantly running wouldn't be very performing especially in a large library. Good to know either way thanks for letting me know, always curious what's up and coming for TS, love 2.0+ you guys are killing it, nice work :) @DanielRosenwasser

@AlexGalays
Copy link

Regarding Object.assign and object spread/rest; please consider we also need to be able to type nested merges, not just shallow ones. rest params is handy for shallow merges, but is a pain for deep ones (e.g, this is an improvement, but doesn't remove the need for some kind of optional operator)

@mhegazy
Copy link
Contributor

mhegazy commented Aug 30, 2016

Regarding Object.assign and object spread/rest; please consider we also need to be able to type nested merges, not just shallow ones.

The deep merge is already handled by intersection types today.

declare function merge<T, U>(o1: T, o2: U): T & U;

merge({ prop: { a: 0 } }, { prop: { b: "string" } });  // equivalent to { prop: {a: number, b: string } }

@AlexGalays
Copy link

Sorry, wrong word. I meant, deep update without any type transformation for the output.

@mhegazy
Copy link
Contributor

mhegazy commented Aug 30, 2016

that is the proposed optionalize operator, e.g. React's SetState

declare function setState<T>(template: T, override: optionalize T): T;

setState({ prop: { a: 0 } }, { prop: { b: "string" } }); // Error {b:string} not assignable to {a:number}

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

4 participants