-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
No "sidecasting" #16995
Comments
That's a sidecast (?), not a downcast. There's no relation whatsoever between |
And the earlier interfaces were empty, so they were simply recasts. TypeScript is a structural type system, not a nominal one. |
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. |
Ah.
In C# interface A {}
interface B {}
class AandB : A, B {}
public class Program {
public static void Main() {
AandB x = new AandB();
A y = x; // upcast
B z = y as B; // downcast/sidecast/whatever
}
} Similarly in Java. Can't really find anything about C# or Java sidecasting, so IDK what the proper terms are.
Huh. const x1 = {};
const y1 = {a:0};
const x2 = x1 as {b:number}; // okay
const y2 = y1 as {b:number}; // not okay That sems weird. The presence of Maybe this makes sense; it just seems foreign and odd at first glance. |
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 If you wanted to explore the assignability of interfaces and classes, you would have to have them be structurally different: interface A { a: string; }
interface B { b: string; }
class AandB implements A, B { a = 'foo'; b = 'bar'; }
const u: AandB = new AandB;
const v: A = u; // this is fine
const w: B = v as B; // 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 Type const x1 = {};
Object.defineProperty(x1, 'b', { value: 0, enumerable: true, configurable: true, writable: true }); Now, Of course this would work... const x1 = {};
const y1 = {a:0};
const x2 = x1 as {b:number}; // okay
const y2 = y1 as {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.
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.
// okay
const a1 = {};
const a2 = a1 as {b:number};
// not okay
const b1 = {a:0};
const b2 = b1 as {b:number}; I understand what tsc is doing, but it seems highly unintuitive. Why should the presence or absence of |
Huh? This isn't legal in C#. For example, you can't cast from 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 |
@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. |
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.
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. |
TypeScript Version: 2.4
Code
Playground
Expected behavior:
Both downcasts compile without error.
(Alternatively, neither downcast compiles.)
Actual behavior:
The class downcast compiles. The object literal downcast for
z
does not compile.FYI,
u: A & B
also worksWorkaround:
Upcast to
Object
first.The text was updated successfully, but these errors were encountered: