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

Intersection types #3622

Merged
merged 14 commits into from Jul 6, 2015

Conversation

Projects
None yet
@ahejlsberg
Copy link
Member

commented Jun 25, 2015

This PR implements intersection types, the logical complement of union types. A union type A | B represents an entity that has either type A or type B, whereas an intersection type A & B represents an entity that has both type A and type B.

Type Relationships

  • Identity: A & A is equivalent to A.
  • Commutativity: A & B is equivalent to B & A (except for call and construct signatures as noted below).
  • Associativity: (A & B) & C is equivalent to A & (B & C).
  • Supertype collapsing: A & B is equivalent to A if B is a supertype of A.

Assignment compatibility

  • A & B is assignable to X if A is assignable to X or B is assignable to X.
  • X is assignable to A & B if X is assignable to A and X is assignable to B.

Properties and Index Signatures

The type A & B has a property P if A has a property P or B has a property P. If A has a property P of type X and B has a property P of type Y, then A & B has a property P of type X & Y.

Index signatures are similarly intersected.

Call and Construct Signatures

If A has a signature F and B has a signature G, then A & B has signatures F and G in that order (the order of signatures matter for purposes of overload resolution). Except for the order of signatures, the types A & B and B & A are equivalent.

Precedence

Similarly to expression operators, the & type operator has higher precedence than the | type operator. Thus A & B | C & D is parsed as (A & B) | (C & D).

Primitive Types

It is possible to intersect primitive types (e.g. string & number), but it is not possible to actually create values of such types (other than undefined). Because such types can result from instantiation of generic types (which is performed lazily), it is not possible to consistently detect and error on the operations that create the types.

Examples

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U> {};
    for (let id in first) {
        result[id] = first[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            result[id] = second[id];
        }
    }
    return result;
}

var x = extend({ a: "hello" }, { b: 42 });
var s = x.a;
var n = x.b;
type LinkedList<T> = T & { next: LinkedList<T> };

interface Person {
    name: string;
}

var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;
interface A { a: string }
interface B { b: string }
interface C { c: string }

var abc: A & B & C;
abc.a = "hello";
abc.b = "hello";
abc.c = "hello";

interface X { x: A }
interface Y { x: B }
interface Z { x: C }

var xyz: X & Y & Z;
xyz.x.a = "hello";
xyz.x.b = "hello";
xyz.x.c = "hello";

type F1 = (x: string) => string;
type F2 = (x: number) => number;

var f: F1 & F2;
var s = f("hello");
var n = f(42);

Fixes #1256.

@@ -1538,8 +1539,8 @@ namespace ts {
else if (type.flags & TypeFlags.Tuple) {
writeTupleType(<TupleType>type);
}
else if (type.flags & TypeFlags.Union) {
writeUnionType(<UnionType>type, flags);
else if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) {

This comment has been minimized.

Copy link
@DanielRosenwasser

DanielRosenwasser Jun 25, 2015

Member

There's a UnionOrIntersection

types: NodeArray<TypeNode>;
}

export interface UnionTypeNode extends UnionOrIntersectionTypeNode { }

This comment has been minimized.

Copy link
@CyrusNajmabadi

CyrusNajmabadi Jun 25, 2015

Contributor

add brands so that these UnionTypeNode and IntersectionTypeNode are distinguished and not assignable to each other.

@@ -1558,7 +1563,7 @@ namespace ts {
instantiations?: Map<Type>; // Instantiations of generic type alias (undefined if non-generic)
mapper?: TypeMapper; // Type mapper for instantiation alias
referenced?: boolean; // True if alias symbol has been referenced as a value
unionType?: UnionType; // Containing union type for union property
containingType?: UnionOrIntersectionType; // Containing union or intersection type for synthetic property

This comment has been minimized.

Copy link
@CyrusNajmabadi

CyrusNajmabadi Jun 25, 2015

Contributor

align comments.

@@ -1706,9 +1714,13 @@ namespace ts {
resolvedProperties: SymbolTable; // Cache of resolved properties
}

export interface UnionType extends UnionOrIntersectionType { }

This comment has been minimized.

Copy link
@CyrusNajmabadi

CyrusNajmabadi Jun 25, 2015

Contributor

Add brands.

@@ -1479,7 +1484,7 @@ namespace ts {
Merged = 0x02000000, // Merged symbol (created during program binding)
Transient = 0x04000000, // Transient symbol (created during type check)
Prototype = 0x08000000, // Prototype property (no source representation)
UnionProperty = 0x10000000, // Property in union type
SyntheticProperty = 0x10000000, // Property in union or intersection type

This comment has been minimized.

Copy link
@CyrusNajmabadi

CyrusNajmabadi Jun 25, 2015

Contributor

we use "UnionOrIntersection" elsewhere. It seems like another place we'd want to use them. i.e. "UnionOrIntersectionProperty". "Synthetic" is odd, as it's a term we don't use consistently for this concept elsewhere.

This comment has been minimized.

Copy link
@yuit

yuit Jun 26, 2015

Contributor

Agree with @CyrusNajmabadi. After reading the code, it will be much easier to read if it is name UnionOrIntersectionProperty

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 29, 2015

Contributor

Yeah, also instantiated properties are just as synthetic as union/intersection properties.

else if (type.flags & TypeFlags.Union) {
writeUnionType(<UnionType>type, flags);
else if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) {
writeUnionOrIntersectionType(<UnionType>type, flags);

This comment has been minimized.

Copy link
@CyrusNajmabadi

CyrusNajmabadi Jun 25, 2015

Contributor

Odd cast to <UnionType>.

@@ -3120,6 +3142,9 @@ namespace ts {
else if (type.flags & TypeFlags.Union) {
resolveUnionTypeMembers(<UnionType>type);
}
else if (type.flags & TypeFlags.Intersection) {
resolveIntersectionTypeMembers(<UnionType>type);

This comment has been minimized.

Copy link
@CyrusNajmabadi

CyrusNajmabadi Jun 25, 2015

Contributor

bad cast to <UnionType>. Brands would help catch this.

}

function getPropertiesOfType(type: Type): Symbol[] {
type = getApparentType(type);
return type.flags & TypeFlags.Union ? getPropertiesOfUnionType(<UnionType>type) : getPropertiesOfObjectType(type);
return type.flags & TypeFlags.UnionOrIntersection ? getPropertiesOfUnionOrIntersectionType(<UnionType>type) : getPropertiesOfObjectType(type);

This comment has been minimized.

Copy link
@CyrusNajmabadi

CyrusNajmabadi Jun 25, 2015

Contributor

wrap this line. it is too long for github. I recommend the form:

return type.flags & TypeFlags.UnionOrIntersection
    ? getPropertiesOfUnionOrIntersectionType(<UnionOrIntersectionType>type)
    : getPropertiesOfObjectType(type);

This also has the benefit of being able to easily see the difference in the branches.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 30, 2015

Contributor

Yes

@@ -3062,6 +3064,26 @@ namespace ts {
setObjectTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexType, numberIndexType);
}

function intersectTypes(type1: Type, type2: Type): Type {
return !type1 ? type2 : !type2 ? type1 : getIntersectionType([type1, type2]);

This comment has been minimized.

Copy link
@DanielRosenwasser

DanielRosenwasser Jun 25, 2015

Member

I'd really prefer this was split into

if (!type1) {
    return type2;
}

if (!type2) {
    return type1;
}

return getIntersectionType([type1, type2]);

But if you're really set on keeping this as one statement, this might be easier on the eyes:

return (!type1 && type2)
    || (!type2 && type1)
    || getIntersectionType([type1, type2]);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 30, 2015

Contributor

I actually find the way @ahejlsberg wrote it clearer than the && style. But I'm also okay with the longhand you suggested.

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Jun 30, 2015

Author Member

I'm going to keep it the way it is, making it longer doesn't really make it any clearer.

result.push(unionProp);
function getPropertiesOfUnionOrIntersectionType(type: UnionOrIntersectionType): Symbol[] {
for (let current of type.types) {
for (let prop of getPropertiesOfType(current)) {

This comment has been minimized.

Copy link
@yuit

yuit Jun 26, 2015

Contributor

nit: property instead of prop

}
if (!props) {
props = [prop];
if (prop && !(getDeclarationFlagsFromSymbol(prop) & (NodeFlags.Private | NodeFlags.Protected))) {

This comment has been minimized.

Copy link
@yuit

yuit Jun 26, 2015

Contributor

So for intersection type, if the property is private or protected, the property will not be included?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Jun 27, 2015

Author Member

Correct, as is the case for union types. When a private or protected property with a particular name occurs in only one constituent type, it is pretty clear that the property should be excluded. It is bit less clear what should happen in cases like the following:

class C {
    private x: { a: string };
}

interface I {
    x: { b: string };
}

var obj: C & I;

Currently obj appears to have no property x, but you could argue that it should have a property x of type { private a: string, b: string } or something like that.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 30, 2015

Contributor

I think the old structure was easier to read, but doesn't matter much.

@JsonFreeman

This comment has been minimized.

Copy link
Contributor

commented Jun 29, 2015

Nice!

I have not yet looked at the implementation, but I have a few comments about the design summary:

  • You mention that primitives can be intersected. I assume any kind of type can be intersected, including union types, right?
  • How does type argument inference work with intersection types? It is similar to union types?
  • How does contextual typing work with intersection types? It is similar to union types?
  • When you say "A & B is assignable to A and assignable to B", seems like it is more generally correct to say "A & B is assignable to X if A is assignable to X or B is assignable to X. When X is A or B, this falls out from reflexivity.
  • The call and construct signatures work out much nicer here than they do for union types.
@@ -1639,6 +1645,8 @@ namespace ts {
StringLike = String | StringLiteral,
NumberLike = Number | Enum,
ObjectType = Class | Interface | Reference | Tuple | Anonymous,
UnionOrIntersection = Union | Intersection,
StructuredType = ObjectType | Union | Intersection,

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 29, 2015

Contributor

Can you add a comment explaining why these are considered structured?

@ahejlsberg

This comment has been minimized.

Copy link
Member Author

commented Jun 30, 2015

@JsonFreeman Some answers:

You mention that primitives can be intersected. I assume any kind of type can be intersected, including union types, right?

Correct. In particular, because type parameters can be intersected we have to allow intersections of all kinds of types.

How does type argument inference work with intersection types? It is similar to union types?

It is similar to union types on the source side: If the source type is a union or intersection type, we infer from each of the constituent types. We currently make no inferences if the target is an intersection type, but I'm thinking we should probably infer to each constituent type that isn't a type parameter, similar to what we do for a union type. However, the secondary inference we make to a single naked type parameter in a union type doesn't seem to make sense for an intersection type.

How does contextual typing work with intersection types? It is similar to union types?

We do nothing special when an intersection type is a contextual type, i.e. it appears to have the same properties with the same types as in an expression. Actually, it's interesting that when a union type A | B | C is used as a contextual type we treat it the same as A & B & C.

When you say "A & B is assignable to A and assignable to B", seems like it is more generally correct to say "A & B is assignable to X if A is assignable to X or B is assignable to X. When X is A or B, this falls out from reflexivity.

Yup.

The call and construct signatures work out much nicer here than they do for union types.

Indeed, but only as long as we preserve order of constituent types.

@JsonFreeman

This comment has been minimized.

Copy link
Contributor

commented Jun 30, 2015

However, the secondary inference we make to a single naked type parameter in a union type doesn't seem to make sense for an intersection type.

I agree, because in the union case, you are saying that maybe the target is a type parameter, and if it is, the whole source type should be inferred to this type parameter. For intersection, the target type is definitely a bunch of things, including a type parameter, but you don't really know "how much of the source type" should be inferred to the type parameter portion. I guess intersection has this element of partiality, where union types are more all-or-none.

Actually, it's interesting that when a union type A | B | C is used as a contextual type we treat it the same as A & B & C.

Yes, I remember thinking that when we came up with contextual typing for union types. The mechanism for union types is rather intersection-y.

Indeed, but only as long as we preserve order of constituent types.

Actually, we have a similar commutativity hole in union types, but it has to do with subtype reduction. It's similar to #1953:

interface A {
    (): string;
}
interface B {
    (x?): string;
}

Ideally, A | B should have a call signature with no parameters, but B | A should have a call signature with an optional parameter. This is different from the intersection case because it has to do with subtype reduction, whereas the commutativity hole for intersection does not. But we should probably address this commutativity hole with union types (possibly by changing subtype for optional parameters).

function parseUnionTypeOrHigher(): TypeNode {
let type = parseArrayTypeOrHigher();
if (token === SyntaxKind.BarToken) {
function parseUnionOrIntersectionType(kind: SyntaxKind, parseConstituentType: () => TypeNode, operator: SyntaxKind): TypeNode {

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 30, 2015

Contributor

Nice reuse!

writeSpace(writer);
}
writeType(types[i], union ? TypeFormatFlags.InElementType : TypeFormatFlags.None);
writeType(types[i], delimiter === SyntaxKind.CommaToken ? TypeFormatFlags.None : TypeFormatFlags.InElementType);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 30, 2015

Contributor

I think you need to ensure that a union type is parenthesized if it is a constituent of an intersection type. Just like how we do for an array element type that is a union or intersection.

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Jun 30, 2015

Author Member

No, we already do that.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 30, 2015

Contributor

Where?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Jun 30, 2015

Author Member

Right here. If the delimiter is not comma, we pass the TypeFlags.InElementType flag which will cause parentheses to be added.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 30, 2015

Contributor

Oh right. In that case, can you change the name InElementType to something else? That's what confused me. What this flag is actually trying to say is that the type list is itself a type.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 30, 2015

Contributor

Maybe IsConstituentOrArrayElement

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Jun 30, 2015

Author Member

Hmm, not sure that really makes it any better. Would prefer to keep it the way it is. All of this logic is due for a revision anyway after we separate out the declaration file generator.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 30, 2015

Contributor

It's just that everywhere else we call it a constituent, and here we call it an element.

callSignatures = concatenate(callSignatures, getSignaturesOfType(t, SignatureKind.Call));
constructSignatures = concatenate(constructSignatures, getSignaturesOfType(t, SignatureKind.Construct));
stringIndexType = intersectTypes(stringIndexType, getIndexTypeOfType(t, IndexKind.String));
numberIndexType = intersectTypes(numberIndexType, getIndexTypeOfType(t, IndexKind.Number));

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 30, 2015

Contributor

resolveUnionTypeMembers has dedicated functions getUnionSignatures and getUnionIndexType. Is there a reason resolveIntersectionTypeMembers has a different shape? I do not understand why this one has a loop, but resolveUnionTypeMembers delegates to other functions to do the loop. Can we make them the same?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Jun 30, 2015

Author Member

Our rules for signatures in unions and signatures in intersections are quite different and there really isn't an opportunity for sharing. The logic for index types could potentially be made more similar, but not much would be gained and we already need the loop for signatures.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 30, 2015

Contributor

Yeah I looked again, and I think you're right.

return result;
// The properties of a union type are those that are present in all constituent types, so
// we only need to check the properties of the first type
if (type.flags & TypeFlags.Union) {

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Jun 30, 2015

Contributor

Interesting way to keep the same loop body, but conditionalize whether you loop or just use the first element.

@tinganho

This comment has been minimized.

Copy link
Contributor

commented Aug 9, 2015

When reasoning about JavaScript objects, the full set of possible values for the type {} consists of objects with any set of properties with any spelling and any value (obviously a very large set).

If we begin with {} which is endlessly big. And it has the values { a: any }, { b: any } and { a: any, b: any } amongst the endless values. I still don't agree that { a: any, b: any } represents an intersection of { a: any } and { b: any }. It's like an intersection of [1] and [2] becomes [12] when it should in fact be [].

@JsonFreeman

This comment has been minimized.

Copy link
Contributor

commented Aug 9, 2015

@tinganho See if the example at #3622 (comment) and my reply #3622 (comment) clarify why combining the properties gives you the intersection.

@tinganho

This comment has been minimized.

Copy link
Contributor

commented Aug 9, 2015

@JsonFreeman After reading it a couple of times. I think I understand your reasoning. All objects are extensible and when doing an intersection it will intersect between the possible extension values. Though I must admit that it wasn't so straightforward.

I think most people think of TS interfaces as not extensible if you don't extend it. You can't for instance not do:

interface A {
    a: boolean
}
let a: A;
a.b = true; // error

So the naming of intersection is still a little bit confusing for me.

@wgebczyk

This comment has been minimized.

Copy link

commented Aug 9, 2015

@JsonFreeman "And this is an intersection because by virtue of having both properties a and b , an object satisfies both constraints of having property a and having property b".

What about that sentence: "And this is an intersection because by having both elements a and b, an set satisfies both constraints of having element a and having element b".

It seems that in your worlds of language composition, system types, compilers and parsers, intersection word has different meaning that for regular developer (like me) that was designing only every simple DSL languages in area of "language design". I accept fact that in your domain you have different languages that might share same written words.

The question is, do you want ot leak internal "language composition" term that collides with the same work for costumers of TS. On the other hand if you want to keep "intersection" only in design documents and do not advertise this language feature to consumers under that name, I'm completely fine!

I as regular developer that comes from typed languages, had a few times problem to understand advertised features (let me mention, guard expressions or type aliases...). I just want to tell you that some things for you might be obvious, are not so obvious to group of people that seems to be target group of TS consumers nada I'm sure that this group is not small and at same time its not majority. Go talk to some UX person on technical and perceived point of views.

Or maybe my assumption that I belong to some group that has never been envisioned to be "target group"?

@rotemdan

This comment has been minimized.

Copy link

commented Aug 9, 2015

@wgebczyk

The confusion is not just related to terminology, but also with the implicit assumption taken by @ahejlsberg and @JsonFreeman that the underlying types are non-strict, which may be different from the intuition and possibly the expectations held by most programmers used to typed languages (myself included, at least initially). This is a conceptual, not a naming issue, that has to do with the scope on which intersections are defined and useful on.

The intersection of two strict (i.e. "closed" or precisely defined) object interfaces A and B would always yield the empty set with the exception being where Val(A) is a subset of Val(B) or Val(B) is a subset of Val(A) (where Val(X) is the set of values satisfying type X). [Edit: this assertion ignores optional properties, for simplicity. Also, strict function interfaces would respond a bit differently to intersection]

Most programmers coming from static (or even more generally, typed) languages tend to see most types as strict, possibly creating a (justified) sense of confusion and uncertainty when confronted with the "intersection" terminology (and concept):

  • Classes - strict in C#, Java, C++ and in TypeScript as well, though intersection is not defined on them and there is no current way to extract interfaces from them.
  • Structs - strict in C#, C++ and C (don't exist in Java). Implemented with non-strict interfaces in TypeScript (or possibly with classes, but that's a less useful approach because classes do not support optional properties or literal assignments).
  • Delegate types (function signature types) - strict in C# (don't exist in C++, not sure about Java). Implemented with non-strict function interfaces in TypeScript.
  • Interfaces - in C# and Java they are not purely strict but still nominal (relation with a supertype needs to defined explicitly, rather than being implicitly inferred from the structure), so they are somewhat non-strict, but in a much weaker way. Non-strict in TypeScript.

The new strict object literal assignment behavior in 1.6 (which is a breaking change) will reduce the need to define purely strict types to some degree, but would still not eliminate it completely. Regardless of this new feature (or any one following it), the introduction of an operator that behaves very poorly with strict types is a strong limitation that would permanently reduce the potential of the language and type system to evolve and develop with time.

@wgebczyk

This comment has been minimized.

Copy link

commented Aug 9, 2015

Hmm.. Maybe I overreact. I'll stop giving feedback on new features and only report serious issues/bugs. I'll ignore all features that makes no sense for me and use in way that satisfy my needs. In worst case when TS will drift too much from my expectations, I'll switch to ES6 with some static analysis tools.

I'm sure there are people that would find TS concepts more appealing :)

Thanks for your time for trying to explain me TS!

@rotemdan

This comment has been minimized.

Copy link

commented Aug 10, 2015

@wgebczyk
I understand your frustration with the complexity and abstractness of all this. I too feel that it is vastly unnecessary, and that's exactly the reason why I wasted so much of my (unrewarded and unpaid) time thinking about this feature.

I proposed a conceptually simpler alternative based on extension (the "extension" operator) that would instead rely on existing logic and facilities in the language for inheritance and may even share its codebase. An extension operator would be more in-line with the intention of the developer for common use cases and Javascript idioms, and much simpler in concept:

Take interface A. Take interface B. Extend A with B. Done. 

Simple, intuitive and works almost exactly like the familiar, and well understood inheritence operation (though it would need to be more permissive with regards to cycles - probably ignore them to allow things like A extend A - the actual operator could be a symbol, not necessarily a whole word). This could be applied both with object and function interfaces. No issues with strictness whatsoever - it has nothing to do with it (though there would need to be some default logic to determine the strictness of a resulting type, based on the strictness of the operands).

As far as I can tell, my proposal has been rejected without explanation. I was advised to post it as a "feature request" but there is no point - If intersection is finalized into the language then there is no room for another operator that behaves almost exactly the same in most cases (at least until strict types are involved).

@wgebczyk

This comment has been minimized.

Copy link

commented Aug 10, 2015

Off Topic & Rants:
@rotemdan I have impression that Mr. @ahejlsberg has drifted too much from initial concept of "introduction of type safety/strict typing". I can understand that after years of creating really good languages, he needed to refresh himself and we got what we have - too far from typed languages too close to weirdness of JS BUT advertised as typed language on top of JS. I can understand that diving into JS so poorly designed, redesigned, with ad hoc changes was challenging, but I think it would be clearly stated that it has less common with C/C++/C#/Java and more with some new concept.
It was my mistake to form some expectations that were never explicitly written: "the guy from rock solid C# (but not perfect) that is pleasure to use will take this crap Wild Wild West JS and set new law. I was using Turbo Pascal then Delphi and now I'm using C#, so new JS called TS will be better world to play with web-thing". Unfortunately AH experiment was based on completely different assumptions. There are really nice features of TS comparable to C# at same time allows to use a few nice features of JS.
They introduced for example union types AND guard expressions AND advertised is really closely together in examples and blog entries. It looked as [aaa|bbb|ccc] and differenating them by [if typeof ...] was only mirage, because it worked only of JS friendly "types" (string, number function, etc) not elements you are "|" - finally it was rather niche feature that a lof of people have problem to use until they realize its big limitations.
They introduced type aliases and i thought as kind of "#define" or "typedef", but again it had some limitations and I falled back to use "import" that is more "define local copy of type(?)" that simple alias.
I could rant on other features like lack of globbing (which hurts a lot when you have a few projects with shared folders and 50k loc & 200k gen loc), delaying release to align with VS, etc.

But still I accept the fact that there is a lot of people that will like and catch on the fly the TS design, its just not for me :)

@kitsonk

This comment has been minimized.

Copy link
Contributor

commented Aug 10, 2015

I am sure subjective personal attacks on the overall direction have a better place than a merged commit. TypeScript being a fully open source project does have a very simple solution if your "morals" have been offended beyond repair...

@jonathandturner

This comment has been minimized.

Copy link
Contributor

commented Aug 10, 2015

Hi all - can we keep the conversation focused on the technical aspects rather than opinion pieces or attacks on people?

I'm all for exploring designs and their technical trade-offs. This is goodness.

But please hold off from attacking people.

Likewise, TypeScript has a particular design philosophy. It intentionally doesn't try to match other languages/systems, instead opting to type common JS patterns. You can see more info on the design philosophy on the wiki (notably note #1 of the Non-goals): https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

@LukaHorvat

This comment has been minimized.

Copy link

commented Sep 9, 2015

What's the observable difference between the types

{
    f1: int
    f2: string
}

and

{
    f1: int
} &
{
    f2: string
}

?

If they're functionally the same, could the latter one just be reduced to the former one? I've found the feature to be of great use for sort of extensible records but my types get needlessly big when I append to them.

@ahejlsberg

This comment has been minimized.

Copy link
Member Author

commented Sep 9, 2015

@LukaHorvat There shouldn't be any semantic differences between the two. However, from a compiler performance point of view, an intersection type made up of many small object types definitely adds more computational load than a single large object type. And, as you point out, the intersection gets messier to look at in error messages and hints. The compiler doesn't attempt to reduce intersection types because it involves analysis and detection of circularly dependent types and can't be done fully when generics and type parameters are involved. We could potentially do something in simple cases, but I'm not sure the effort is worth it.

@LukaHorvat

This comment has been minimized.

Copy link

commented Sep 9, 2015

I don't know how common my use case is but it might be the majority. My records are simple and fully typed and I add to them in various steps in my processing pipeline. Super convenient.
I hope this reduction does get implemented in the end.

@jbondc

This comment has been minimized.

Copy link
Contributor

commented Sep 15, 2015

Thoughts on changing

If A has a property P of type X and B has a property P of type Y,
 then A & B has a property P of type X & Y.

to

If A has a property P of type X and B has a property P of type Y,
 then A & B has a property P of type Y.
If A has a property P of type X and B has a property P of type Y, 
then B & A has a property P of type X.

So the last declared type wins, see #4805

That gives an explicit way to resolve conflicts:

type oNumber = { a: number, num: number }
type oString = { a: string, str: string }
type oBoolean = { a: boolean, bool: boolean }
type merge = oNumber & oString & oBoolean; // { a: boolean, num: number, str: string, bool: boolean }

type resolve = merge & {a: number | string | boolean } // { a: number | string | boolean, num: number, str: string, bool: boolean }

Right now this looks super weird:
snap

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
You can’t perform that action at this time.