Description
Relate Control Flow to Conditional Types in Return Types
#33912
(continued from #55754)
function createLabel<T extends string | number>(
idOrName: T,
): T extends string ? { name: string } : { id: number } {
if (typeof idOrName === "number") {
return { id: idOrName };
}
return { name: idOrName };
}
-
Can't easily implement these functions because we'll error on the return statements.
- Need a type assertion.
-
Conditional types are not exactly accurate in what they can model. Your runtime type might not align with your design-time type.
function foo<T>(x: T): T extends number ? number : string; { if (typeof x === "number") return x; return ""; } let x: unknown = 42; foo(x); // has type string?
unknown
doesn't extendnumber
, but it could actually be one!
-
Roughly, conditional types aren't safe when the false-most branch doesn't contain (isn't a supertype of) the types in true branches.
- ...or the conditional type doesn't check directly against the constrained types in some way.
- Saved by conditional type distribution (otherwise also has issues!)
- Constructivist logic sure is annoying!
- It is easy to get this wrong.
- ...or the conditional type doesn't check directly against the constrained types in some way.
-
This is a problem because you need this to accurately describe what an
if
statement does. -
Could say the result is effectively a union when the types are comparable? Or intersection is non-vacuous?
-
Another concept - some way to express negation of literals and primitives.
- Cannot carve out the object hierarchy.
-
These two ideas feel a bit tied - usually you can't say anything conclusively about intersections of object types being vacuous. You can always create new types by intersecting. But you can make statements regarding primitives.
-
It's probably too late to change conditional types. But there's forms of conditional types that are safe. And you could perform the narrowing when you encounter those.
-
Is it worth pushing on this space?
- Well, the
extends
in a conditional type doesn't directly correspond to what a function will actually do. So that's not safe either. You'd need some way to relay what expressions do in type space. - Also, people needing to write this stuff explicitly - it becomes a burden as well.
- Well, the
-
Does type argument inference throw a wrench in here?
- Doesn't feel like it, but possibly.
-
Feel is "too complex" but we're possibly not coming at this with a solution-oriented approach.
- Intentionally picked a bad example.
- But there's a lot of road bumps towards a good solution. Needs negated types, and special form of conditional types, and more accurate predicate in conditional types, and...
-
Want to come back to this convo while showing off what's actually enabled by the current prototype.
Emitting an Ambiguity Error When Declaration Emit Will Differ
export const blah = Math.random() < 0.5 ? "foo" : "bar";
- Variable is a literal union in the project, widened to
string
in declaration emit. - Destructuring,
&&
,||
, and??
have some different paths. - Erroring seems like a reasonable short-term solution - but would like to reconsider another time in a way that doesn't error.
Activity