You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
That's a sidecast (?), not a downcast. There's no relation whatsoever between y's declared type {a: number} and the target type { b: number } - neither is a supertype nor subtype of the other. You can only type-assert between types where one is a subtype or supertype of the other.
Talking about sidecasting, shouldn't we take a second look at #14156? Sidecasting is allowed for literal types, which is both inconsistent and incorrect, for no apparent reason.
Because it is structural, and not nominal, classes and object literals are duck typed in the sense that if they look like a duck and quack like a duck, they are equivalent. A super type and a sub type are determined by their structural relationship, not their nominal relationship. This is because JavaScript behaves largely in a structural way (with the exception of instanceof operator but let's not go there).
If you wanted to explore the assignability of interfaces and classes, you would have to have them be structurally different:
interfaceA{a: string;}interfaceB{b: string;}classAandBimplementsA,B{a='foo';b='bar';}constu: AandB=newAandB;constv: A=u;// this is fineconstw: B=vasB;// nope, this is now a side cast
Also, casts are casts in TypeScript. Basically if the compiler allows it, it trusts what you are saying. This is because, even with good CFA, there are some assignments that can't be tracked by the type system, because JavaScript is so malleable... So unless it knows for certain you are doing something wrong, it will stop you, but even then, then you might be tempted to cast as any to really just go a f* it, stop bugging me.
Type {} relates to type { b: number } (I am not so good on if it is strictly super or sub) but { b: number } and { a: number } are unrelated types, therefore TypeScript disallows it. To demonstrate though how{} could actually be { b: number } at runtime, where TypeScript wouldn't notice it would be something like this:
Now, x1 contains a property b with a value of 0. TypeScript doesn't know that. Yeah, if you are coming from C# or Java, it is foreign. For those of us who have been living in JavaScript land, we are so used to a weakly typed language, that TypeScript doesn't seem so foreign.
Of course this would work...
constx1={};consty1={a:0};constx2=x1as{b:number};// okayconsty2=y1as{a: number;b:number};// this is fine!
I completely understand structural typing (though I wasn't aware TS had it). Many type systems have it (Go, Scala).
The main issue comes from not from structural typing but casting.
Super
/ \
SubA SubB
Every type system with casting I know of (C, C++, Java, C#, Go, Scala, Closure JS, Flow) allows casting from SubA to SubB (as long as the SubA & SubB intersection is non-empty).
But TS does not.
{}
/ \
{a:number} {b:number}
// okayconsta1={};consta2=a1as{b:number};// not okayconstb1={a:0};constb2=b1as{b:number};
I understand what tsc is doing, but it seems highly unintuitive. Why should the presence or absence of a:number in the original type influence the castability to {b:number}?
Flow doesn't even allow downcasting, let alone casting between two types with no relationship. Same with Scala as far as I can tell.
The intuition here is that your type assertion need to be plausible. It's plausible that someone gave you a HTMLElement that you know to actually be a HTMLDivElement. It's not plausible that someone gave you a Truck and you actually know it to be a Dog - there's a difference between refining to a subtype and just "let's make this a totally different type".
Button and TextBox are classes and C# types are such that a Button can never, ever, ever be a TextBox (well, except for null which is simultaneously everything).
And I completely agree that it's good to reject casts between "totally different" types that share no instances.
But if Button and TextBox are interfaces, then the intersection is not empty and it does work.
Same for Java, same for Scala. (I guess not for Flow -- my mistake.)
It's not plausible that someone gave you a Truck and you actually know it to be a Dog
Typescript says that JanitorPerson isn't castable to StudentPerson, even though Person is castable to StudentPerson and JanitorPerson is a Person. Were you to have Person, not specifically JanitorPerson, only then could you cast to StudentPerson.
It may not technically violate the Liskov substitution principle (since that operates on instances not types), but it comes pretty close.
And I completely agree that it's good to reject casts between "totally different" types that share no instances.
It is plausible, assuming a TruckDog is possible.
You can always synthesize some type that is a subtype of two other types. The two statements of "Some assertions should be rejected" and "You should allow it if some possible go-between type may exist" are contradictory.
Not in general. Your example proved C# rejects known impossibilities.
You can always synthesize some type that is a subtype of two other types.
No. For example, you can't have a number & function subtype.
I concede the point. Any casting except upcasting is undesirable. So if TS chooses very special rules and is more awkward that other systems for casts, it's not a big deal.
Probably there are many other things more important things do to.
Activity
RyanCavanaugh commentedon Jul 7, 2017
That's a sidecast (?), not a downcast. There's no relation whatsoever between
y's declared type{a: number}and the target type{ b: number }- neither is a supertype nor subtype of the other. You can only type-assert between types where one is a subtype or supertype of the other.kitsonk commentedon Jul 7, 2017
And the earlier interfaces were empty, so they were simply recasts. TypeScript is a structural type system, not a nominal one.
gcnew commentedon Jul 7, 2017
Talking about sidecasting, shouldn't we take a second look at #14156? Sidecasting is allowed for literal types, which is both inconsistent and incorrect, for no apparent reason.
pauldraper commentedon Jul 7, 2017
Ah.
In C#
Similarly in Java. Can't really find anything about C# or Java sidecasting, so IDK what the proper terms are.
Huh.
That sems weird. The presence of
a:numbermakes it no more or less likely to haveb:number.Maybe this makes sense; it just seems foreign and odd at first glance.
[-]Downcasting does not work with object literal types[/-][+]No "sidecasting"[/+]kitsonk commentedon Jul 7, 2017
Because it is structural, and not nominal, classes and object literals are duck typed in the sense that if they look like a duck and quack like a duck, they are equivalent. A super type and a sub type are determined by their structural relationship, not their nominal relationship. This is because JavaScript behaves largely in a structural way (with the exception of
instanceofoperator but let's not go there).If you wanted to explore the assignability of interfaces and classes, you would have to have them be structurally different:
Also, casts are casts in TypeScript. Basically if the compiler allows it, it trusts what you are saying. This is because, even with good CFA, there are some assignments that can't be tracked by the type system, because JavaScript is so malleable... So unless it knows for certain you are doing something wrong, it will stop you, but even then, then you might be tempted to cast
as anyto really just go a f* it, stop bugging me.Type
{}relates to type{ b: number }(I am not so good on if it is strictly super or sub) but{ b: number }and{ a: number }are unrelated types, therefore TypeScript disallows it. To demonstrate though how{}could actually be{ b: number }at runtime, where TypeScript wouldn't notice it would be something like this:Now,
x1contains a propertybwith a value of0. TypeScript doesn't know that. Yeah, if you are coming from C# or Java, it is foreign. For those of us who have been living in JavaScript land, we are so used to a weakly typed language, that TypeScript doesn't seem so foreign.Of course this would work...
pauldraper commentedon Jul 7, 2017
I completely understand structural typing (though I wasn't aware TS had it). Many type systems have it (Go, Scala).
The main issue comes from not from structural typing but casting.
Every type system with casting I know of (C, C++, Java, C#, Go, Scala, Closure JS, Flow) allows casting from SubA to SubB (as long as the SubA & SubB intersection is non-empty).
But TS does not.
I understand what tsc is doing, but it seems highly unintuitive. Why should the presence or absence of
a:numberin the original type influence the castability to{b:number}?RyanCavanaugh commentedon Jul 7, 2017
Huh?
This isn't legal in C#. For example, you can't cast from

ButtontoTextBoxdespite the common heritage ofControl:This isn't legal in Java https://stackoverflow.com/questions/21370179/java-casting-one-subclass-to-another
Flow doesn't even allow downcasting, let alone casting between two types with no relationship. Same with Scala as far as I can tell.
The intuition here is that your type assertion need to be plausible. It's plausible that someone gave you a
HTMLElementthat you know to actually be aHTMLDivElement. It's not plausible that someone gave you aTruckand you actually know it to be aDog- there's a difference between refining to a subtype and just "let's make this a totally different type".pauldraper commentedon Jul 7, 2017
Ryan Cavanaugh (@RyanCavanaugh) it is possible, with my qualication that the SubA & SubB intersection is non-empty .
Button and TextBox are classes and C# types are such that a Button can never, ever, ever be a TextBox (well, except for null which is simultaneously everything).
And I completely agree that it's good to reject casts between "totally different" types that share no instances.
But if Button and TextBox are interfaces, then the intersection is not empty and it does work.
Same for Java, same for Scala. (I guess not for Flow -- my mistake.)
It is plausible, assuming a TruckDog is possible. http://www.littletikes.co.uk/big-dog-truck.html
Typescript says that JanitorPerson isn't castable to StudentPerson, even though Person is castable to StudentPerson and JanitorPerson is a Person. Were you to have Person, not specifically JanitorPerson, only then could you cast to StudentPerson.
It may not technically violate the Liskov substitution principle (since that operates on instances not types), but it comes pretty close.
RyanCavanaugh commentedon Jul 7, 2017
You can always synthesize some type that is a subtype of two other types. The two statements of "Some assertions should be rejected" and "You should allow it if some possible go-between type may exist" are contradictory.
pauldraper commentedon Jul 7, 2017
Not in general. Your example proved C# rejects known impossibilities.
No. For example, you can't have a number & function subtype.
I concede the point. Any casting except upcasting is undesirable. So if TS chooses very special rules and is more awkward that other systems for casts, it's not a big deal.
Probably there are many other things more important things do to.