-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Allow switch type guards #2214
Comments
Yes please allow switch case there for state-machine. Also the conditional/ternary operator ( |
+1 for switch (typeof v) {
case 'number':
logNumber(<number>v);
break;
case 'string':
logString(<string>v);
break;
default:
throw new Error("unsupported type");
}``` |
Approved |
+1 |
Is this the same as #2388? |
Not quite the same thing, since you don't need #2388 for this. |
Is this allow user-defined type guards in switch cases? Like this: interface MyType1 { type: number }
interface MyType2 { test: string }
function isType1 (x): x is MyType1 {
return !!x.type && typeof x.type === 'number'
}
function isType2 (x): x is MyType2 {
return !!x.test && typeof x.test === 'string'
}
let x = getType1OrType2()
switch (true) {
case isType1(x):
console.log('x is MyType1')
break;
case isType2(x):
console.log('x is MyType2')
break;
default:
console.log('Unknown type')
} |
@goodmind Your above suggestion breaks common |
@electricessence ok. i think #165 better for something like my example (but without switch, maybe auto-generated user type guards or so) |
@electricessence what do you mean by "common |
@goodmind You may want to consider Discriminated Unions to solve this kind of problem. They let you get strong typing in switch cases without writing type guards when you have a common string literal type property to discriminate between them. Here's the example from the TypeScript Handbook that I linked to: interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle;
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
} |
I've got some code similar to @icholy's sample function dosothing(exprValue: string | boolean | number | RegExp){
switch (typeof exprValue) {
case 'number':
return setNumber(exprValue);
case 'string':
return setString(exprValue);
case 'boolean':
return setBoolean(exprValue);
}
}
function setNumber(val: number){
// ...
}
function setString(val: string){
// ...
}
function setBoolean(val: boolean){
// ...
} You can see it online in Playground Got this error:
@RyanCavanaugh what's the progress about this topic? |
@icholy in other languages, A side note about |
That's a valid use case.
These are functionally equivalent and the type system should support them both. |
@icholy I understand why you're doing what you are. It's a bit unconventional. But I just wanted to be clear that type-guards in this way may end up being more difficult to implement than an if chain. Here's why C# only allows constants/statics: |
@electricessence I don't personally use that style, but apparently some people do. My point is that the type system should not be limited to what you think people should be doing. edit: not sure why you keep going into the details of |
@icholy I am bringing it up just as a point of contrast. What I think people should or shouldn't be doing is irrelevant. Because JS can evaluate dynamic case values, I agree that it would be nice that your example actually work. I'm only suggesting that 1) it may be more difficult to implement than you may be assuming, and 2) in the scheme of other languages is unconventional and because a working version of it can be written correctly using if blocks, it may be of a lower value to fix compared to other issues/features. Having it work with constants IMO is expected, hence why I'm on this thread. Having it work with dynamic values at run-time would be cool, but I don't have that expectation. Oh and if there's ever any doubt. I love |
@iFreilicht that first part only works because we can "see" the |
Ah yes, you're both right, as evidenced by this: function calc(n: number) { }
let x: string | number = "nope";
switch (typeof x) {
case 'number':
calc(x); //Argument of type 'string' is not assignable to parameter of type 'number'.
} Thanks for clearing that up! |
Any update on enabling the ternary if operator as a type guard? |
@danielmhanover example? That should work already |
@danielmhanover Using type predicates works as you would expect (if I'm guessing your intent correctly): export type SomeUnion = TypeA | TypeB;
function isTypeA(input: SomeUnion): input is TypeA {
return (<TypeA>input).propA !== undefined;
}
export const someFunc = (input: SomeUnion) =>
isTypeA(input)
? onlyTypeA(input)
: otherCase(input); |
Hey, looking at Can one get this example to work with an additional generic type S? interface Shape {
kind: string;
}
interface Square extends Shape {
kind: "square";
size: number;
}
interface Circle extends Shape {
kind: "circle";
radius: number;
}
// adding | S here causes the cases to loose their type and everything is just ShapeType<S>
type ShapeType<S extends Shape> = Square | Circle | S;
function area<S extends Shape>(s: ShapeType<S>) {
switch (s.kind) {
case "square":
return s.size * s.size; // s should be of type Square
case "circle":
return Math.PI * s.radius ** 2; // s should be of type Circle
default:
return 0 // s should be of type S
}
} |
Could we combine Discriminated Unions and Type Predicates to get fully exhaustive, statically checked poor-man's pattern matching in Typescript? I don't think this could generalize to any tagged union, but perhaps it could be a good starting point to get most of the plumbing out of the way? |
I'm considering having a go at the original proposal of switch on function logNumber(v: number) { console.log("number:", v); }
function logString(v: string) { console.log("string:", v); }
function foo1(v: number|string) {
switch (typeof v) {
case 'number':
logNumber(v);
break;
case 'string':
logString(v);
break;
default:
throw new Error("unsupported type");
}
} Is this still wanted by the TS team? |
Yes. |
Great, I'll give it a shot. EDIT: PR submitted |
Just for future reference, there's another similar scenario: switch (obj.constructor) {
case DerivedType:
// obj should be a DerivedType
break;
default:
// obj is still a base type
break;
}
// obj is still a base type |
@mhegazy If this is still on the TS radar, would it be possible to get a reviewer assigned to this issue? |
I'm not sure if @JannicBeck 's issue of discriminated unions is related to this one, but is that being tracked anywhere? I run into the issue he mentions all the time, specifically with Redux. I have a reducer that handles certain actions, but then defers to another reducer of unknown types. |
@lukescott One workaround would be to just cast the general type to a known union type: |
Fix #2214. Support narrowing with typeof in switch condition.
Hi, Is there any way the following can be allowed without error? class A {
aa = 5;
}
class B {
bb = 9;
}
function doStuff(o: A | B): number {
switch (o.constructor) {
case A:
return o.aa; // error!
default:
return o.bb; // error!
}
}
console.log(doStuff(new A())); Please add type guard narrowing with switch case of the |
@miguel-leon I believe your suggestion is tracked by #23274 |
I met a specific case, and I'm wonder if this can be fix. enum Test = {
TestA = 'TESTA',
TestB = 'TESTB',
}
type TestValues = `${Test}`;
type DataType<TypeName> = TypeName extends TestValues
? TypeName extends 'TESTA'
? TestAData
: TypeName extends 'TESTB'
? TestBData
: never
: never;
function exampleForTestA(data: TestAData) {
// do something...
}
function exampleForTestB(data: TestBData) {
// do something...
}
function example<T extends TestValues, D extends DataType<T>>(
type: T,
data: D,
) {
switch (type) {
case 'TESTA':
return exampleForTestA(data as TestAData); // I have to add type assertion to make it work without error
case 'TESTB':
return exampleForTestB(data as TestBData); // same here
default:
throw new Error('Unknown type');
}
} |
I have the following code:
Error:
I was forced to rewrite this using
if
statements.Please allow using switch statements as type guards.
The text was updated successfully, but these errors were encountered: