TypeScript Design Meeting Notes: 4/25/2014

Topics: Language Specification
Apr 28, 2014 at 3:45 PM
Edited Apr 28, 2014 at 3:45 PM
  • Contextual type/BCT change
  • ES6 alignment
    • Symbol types
  • Destructuring
[meeting cut short, so these agenda items were not covered]
  • 'protected'
  • Abstract classes/methods
Contextual Type/BCT Change

Background: Our Best Common Type algorithm currently has some corner cases where no subtype can be found, resulting in the BCT becoming {}, the empty object type. Users with large codebases seem to be tripping over this in moving to 1.0, and there may be a fix we can use in future versions.

Example 1:
interface Point {
    x: number;
    y: number;
var any: any;
// Unexpected error: Cannot convert {}[] to Point[]
var pts: Point[] = [{ x: null, y: any }];
Example 2:
enum Size { Small, Medium, Large }
interface Shirt {
    color: string;
    size?: Size;
// Unexpected error: Cannot convert {}[] to Shirt[]
var sizes: Shirt[] = [{ color: 'blue', size: 1 }];
Example 3:
// Poor error message: Type of conditional '{}' must be identical to 'number', 'number' or 'string'
// Desired error: Cannot convert 'number' to 'string'
var s: string = foo ? 1 : 0;
New proposed Best Common Type algorithm:
  • Check the contextual type to see if it's the supertype of all candidates
  • If it is not, next check all candidates (excluding the contextual type) for BCT
  • This will actually be faster, as many cases will be handled by the more optimized first bullet (O(n) rather than O(n^2))
  • This should fix the issues above, where the contextual type is causing us to lose type information during BCT
  • Should be compatible with existing approach
  • As this is a different algorithm, we still need to vet that this is observably the same as the 1.0 BCT algorithm. More investigation is necessary.

Background: Our current type system treats heterogeneous arrays as arrays of the empty type because of BCT. This won't allow us to get precise type information when users use the ES6 destructuring capabilities. Are there ways we can support this in the type system?
function f() {
    return ["hello", 2];
}  // today returns {}[]

var {a,b} = f();  // not enough to destructure

Arrays can have element types.
var x = ["hello", 2];   // would infer {}[string, number]

// Note: {}[string, number] ==> [string, number, {}, {}, …]

x[0] // type string

x = [];  // also allowed
Key piece of the proposal: this needs to be backward compatible. To do so, we don't check the element types.

Issues to investigate:
  • How do you assign between x[0] and x[i]? We get two different types, even if i == 0
  • Also wouldn't catch x = ["",""]
  • What about x[0] = 2? Would error with this proposal but not with 1.0 semantics. Perhaps we never check the element types, we only use it when destructuring.
What about having a tuple type? This would mean splitting into two forms of the type, {}[string, number] and [string, number]. The former is a heterogeneous array that starts with a string element followed by a number element, with all successive elements being of the type {}. The latter is a 'tuple' that only has the two slots.

We could opt to never infer [string, number] and to only infer {}[string, number]. If you explicitly specify the stricter tuple type, then we enforce it.

Question: what is the canonical form of the tuple type? We don't have a way to put this in an interface.

Action Item: finish a completed proposal that addresses these issues.

Protected Accessibility

Still the top-most voted issue. More requests this week. Request to discuss abstract classes/methods first. Meeting ran out of time before these could be covered.
Apr 28, 2014 at 10:55 PM
Thanks, Jon. It is great to have these meeting notes available to the community. Has the language design committee discussed union types recently? I'd be interested to see what the thoughts were on that. The typed array syntax looks interesting.
Apr 29, 2014 at 2:22 PM
We've talked some about union types just amongst ourselves but not as part of a recent design meeting. There are some examples where .d.ts typings would get much easier to do with them. Next steps there is to put together a proposal to present at a meeting.

The gotcha here is that we'd need to work out exactly how union types affect type inference and what the shape of a union type is (what's the completion list when you dot off it?)
Apr 30, 2014 at 8:42 AM
Hi @jonturner,

Fantastic to hear union types are being discussed. I'll let the rest of the Definitely Typed team know as I know they have opinions on the topic.

My own (not massively developed) thoughts are that the shape of a union type would initially be the "shared sum" of the available types specified. So if a union type specified boolean and string then the resulting type would have both boolean and string properties for the purpose of type inference. eg.
var union: <boolean | string>; // The compiler treats union as a combination of both boolean and string in the absence of further info
Let's use jQuery's val as an example. The val getter returns either String or Number or Array (of strings). Without type unions this getter can only be modelled like this in TypeScript:
    val(): any;
My guess / hope is that with type unions it might look something like this:
    val(): <string | number | string[]>;
And consumption would look like this:
var union = $("#other").val(); // The compiler treats this as a combination of string, number and string[] in the absence of further info
If a specific type could be determined in the consuming file then usage of that type union would resolve to the specified type eg:
var str: string = $("#input1").val(); // The compiler treats this as a string
var num: number = $("#input2").val(); // The compiler treats this as a number
var arr: string[] = $("#select1").val(); // The compiler treats this as a string array

var error: boolean = $("#input1").val(); // The compiler recognises this as an error as boolean is not part of the type union
Something else to consider would allowing the use of type assertions to achieve the same behaviour (and there is possibly some collision with the implementation of generics here but I throw it out as an idea):
var str = $("#input1").val<string>(); // The compiler treats this as a string
var num = $("#input2").val<number>(); // The compiler treats this as a number
var arr = $("#select1").val<string[]>(); // The compiler treats this as a string array

var error = $("#input1").val<boolean>(); // The compiler recognises this as an error as boolean is not part of the type union
Finally if having the compiler combine various types together has hidden troubles I'd still value the concept of union types where the specific type in the type union being used has to be provided in some way. (as in the previous 2 code examples)

This would prevent users (again looking at jQuery's val getter) treating what will definitely be either a string, number or string[] as a boolean for instance.
Apr 30, 2014 at 4:21 PM
Edited Apr 30, 2014 at 4:21 PM
Definitely also happy that union types are being discussed. Good points by @johnny_reilly .

Thanks for posting these meeting notes online!
May 2, 2014 at 6:14 PM
Good points by @johnny_reilly

Union types would be very welcome to create typings that cover more existing JavaScript libraries. I'm not 100% sure how union types are defined, but I am hopeful they can simplify some of the method/type overload scenarios or as a 'shortlist' that limits the need to use any.

I've also seen some cases in the wild for heterogeneous arrays, like tuples (I got a ticket open in the work items). Looking at the meeting report I think it would be great if the implementation provides a way to specify what happens after the specified indices, eg; a pure tuple might have a fixed length, while a heterogeneous array could overflow it's type somehow (like a rest parameter does for function arguments).
May 3, 2014 at 12:35 AM
The example we ended up with on the whiteboard today was:
function foo<T,U>(x: T|U, y: T|U): T {
    /* ... */

var x = foo('', 3);
// What happens here?
// T is string, U is number: x is string
// T is number, U is string: y is number
// T is string|number, U is <anything>: x is string|number
// U is string|number, T is <anything>: x is <anything>?
May 3, 2014 at 8:28 AM
Exciting to hear!

Personally I think this result makes most intuitive sense out of the possibilities listed:
// T is string, U is number: x is string
I say that as it seems to be the embodiment of the rule:
If generic then types are inferred based on the order of supply

In this example a string then a number is supplied:
var x = foo('', 3);
So in foo<T,U> T will be inferred as a string (as supplied first) and U as a number (as supplied second).

By the way, I prefer your syntax of just pipes (|) to represent union types rather than the angle brackets + pipes (< | >) in my examples.
May 3, 2014 at 1:22 PM
Agree with @johnny_reilly.

As for possibilities 3 & 4, it seems like you shouldn't ever bind a generic to a union type unless the type supplied is explicitly a union type.
function foo<T,U>(x: T|U, y: T|U): T {
    /* ... */

var a: string|number = '';
var x = foo(a, 3); //T is string|number, U is number, x is string|number
May 3, 2014 at 5:42 PM
TypeScript does duck typing with object types. As long as members exist, it will be a match. This would also allow passing object literals as parameters to union types (as expected).

FYI: I like the (val:A|B) syntax also, and <|> when casting.
May 5, 2014 at 3:41 PM
I think that because the type of T and U can't be determined the return type should assume the safest possible value, which is {}. I think that still makes sense if U could be considered of type string|number.

It's similar to what TypeScript does in this situation:
declare function foo<T>(input: (param: T) => T): T;
var t = foo(x => x);
The generic type is under-determined so t takes on type {}.
May 14, 2014 at 10:12 AM
More than union type I would be interested in hearing about mixins. Does proposal alike the one described by @jonturner on this thread have been discussed among the typescript team members ?