Skip to content

Design Meeting Notes, 9/27/2023 #55898

Closed
Closed
@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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design NotesNotes from our design meetings

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @DanielRosenwasser@RyanCavanaugh

        Issue actions

          Design Meeting Notes, 9/27/2023 · Issue #55898 · microsoft/TypeScript