Skip to content

Design Meeting Notes, 9/27/2023 #55898

@DanielRosenwasser

Description

@DanielRosenwasser

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 extend number, 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.
  • 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.
  • 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

#55860

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design NotesNotes from our design meetings

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions