Skip to content

Design Meeting Notes, 8/18/2021 #45504

Description

Consistently Filtering Out Negative Matches when Narrowing

#45205
#41821

Playground Samples

  • Reported as a regression in TypeScript 4.3
  • When you're trying to narrow out a generic in opposite branches, you'd expect it to disappear.
  • This PR is a bit of a hack on top of a hack to fix that issue; we can recognize that the types are not identical, but you can defeat this with an intersection.
  • What exactly is the "depth" we're talking about? What stack is being tracked?
  • We have source type IDs and target type IDs that we place onto a stack.
    • As we do more comparisons, we check to see whether a comparison we've already done on the stack veratim is there. If so, we say "maybe" the two original types are related.
    • If the source or target sides have appeared more than N times (where N=5 today), then we mark that side of the comparison as "infinitely expanding", and if both sides are "infinitely expanding", we say the types are "maybe" related.
      • Not 5 of the same type, 5 of the same "recursion identity, where sometimes a recursion identity is a type object.
    • When we think we see a repeating pattern, we say "it's similar enough", and hope some other portion of the type will stop us.
  • Track source and target relationship stack depth seperately, only increase on change in value #41821 is similar in spirit, but feels less like a hack - ensure that if one side isn't changing, we don't prematurely consider the type to be infinitely expanding.
    • So this avoids tracking "runs" of the same recurring type.

    • What do you do when you have two types that link back to themselves?

      interface A {
          next: A;
          data: number;
      }
      
      interface B {
          next: B;
          data: number;
      }
      • Do we push both?
      • We rely on some behavior of how we break down each side.
        • Unclear what this is right now.
  • Changes in baselines for Track source and target relationship stack depth seperately, only increase on change in value #41821?
  • Have concern that we'll break something we haven't thought of.
    • What is the concern? Not issuing an error to issuing an incorrect error?
      • On one side we have a constraint that keeps on repeating but generating new type identities (never see the same type twice), but we keep entering the logic because we never see the type change.

Tagged Template Inferrence and Making TemplateStringsArray

#31422
#33304
#45310

Notes from Ryan Cavanaugh (@RyanCavanaugh)

declare function tag<T extends TemplateStringsArray>(strs: T): T;

let tsa: TemplateStringsArray;
let x = tag `hello world`;
  • tsa looks like a ReadonlyArray<string>, plus a raw property
  • In a function today, you can capture the string contents of a regular string with T extends string
  • No corresponding behavior exists for tagged templates, i.e. x is TemplateStringsArray
  • 36 👍 for the issue
  • PR makes TemplateStringsArray generic, adding TParts and TRawParts
  • With a formatting hole, you get a splice point in the array
  • This is only really interesting if the function returns the array
  • With some galaxy brain type math you can constrain template holes by their corresponding raw parts; neat
  • Implementation is minimal
  • What is this for?
    • Intl?
    • Inviting type system abuse?
  • Use cases here aren't super compelling, but feels like they could be?
    • Let's see more

Activity

  1. DanielRosenwasser commented on Aug 18, 2021

    @DanielRosenwasser
    MemberAuthor

    To add some thoughts to the tagged template examples:

    • Internationalization example = "I want parity between what regular functions provide and tagged template strings:"
      * This is a weak 👍 - we like when the language is more consistent.
    • Favorite markup/query language examples =
      • "I want to write a full HTML parser and provide the same checking that JSX gives"
      • "I want to write a full GraphQL parser and provide exact API shapes back"
      • This is a moderate 👎 - we think we'd be encouraging people to use the wrong tools to solve these problems
  2. trusktr commented on Nov 18, 2021

    @trusktr
    Contributor
    • Use cases here aren't super compelling, but feels like they could be?
      • Let's see more

    Just the use case of

    /** @type {HTMLDivElement} */
    const div = html`<div>
      <${SomeComponent} fooProp=${123 /*fooProp accepts numbers only*/} />
      <${SomeComponent} fooProp=${"bar" /*type error*/} />
    </div>`

    is compelling enough. This means people can write type-safe DOM applications (or other applications in places similar to React native that render a declarative tree to native UI components), including type safety within the template string markup, without requiring a build step to transform JSX. For example, that code snippet could be pasted from VS Code (where the user would be able to catch errors in the html template) to CodePen and it would just work.

  3. trusktr commented on Nov 18, 2021

    @trusktr
    Contributor

    This is a moderate 👎 - we think we'd be encouraging people to use the wrong tools to solve these problems

    I don't think that using a build-less setup is the wrong tool in every case. There are very valid reasons to use no build tools:

    • small apps where the cost of managing build setups is not worth it.
    • one-off solutions where startup time does not matter (f.e. write a kiosk app for a one-time conference gathering, you have all morning to start the app, then it just needs to run)
    • production applications where the performance cost of runtime template compilation doesn't matter.
    • ease of use in getting started: teaching people to write apps while having type safety, getting them off the ground and running without complications, without cognitive overhead from learning build tools (and all the way in which they can fail).
    • etc

    Furthermore, build steps can optionally compile template tag strings to optimized formats just like with JSX, with the benefit that code can be written one way and it would work in both build-less or build-full setups; optimization when needed. That, with added type safety, is a nice win.

  4. tobiasdiez commented on Nov 25, 2021

    @tobiasdiez
    • "I want to write a full GraphQL parser and provide exact API shapes back"

    • This is a moderate 👎 - we think we'd be encouraging people to use the wrong tools to solve these problems

    Why are type-safe tagged template strings the wrong tool for declaring Graphql queries and what would be a better solution? The graphql codgen https://www.graphql-code-generator.com/plugins/gql-tag-operations-preset provides something similar but has to use the somewhat awkward convention gql(/* GraphQL */ query) due to the limitations of TS support for tagged templates.

  5. ExE-Boss commented on Nov 28, 2021

    @ExE-Boss
    Contributor
    • "I want to write a full HTML parser and provide the same checking that JSX gives"
    • "I want to write a full GraphQL parser and provide exact API shapes back"
    • This is a moderate 👎 - we think we'd be encouraging people to use the wrong tools to solve these problems

    Well, a full parser would probably be infeasible anyway due to type instantiation and recursion limits, but a simplified parser that correctly infers the return type of the html`<div>${text}</div>`⁠ expression as HTMLDivElement would be useful.

  6. imjamesb commented on Mar 31, 2022

    @imjamesb

    Please revisit generic type for template literals.

  7. za-creature commented on Nov 15, 2025

    @za-creature

    we think we'd be encouraging people to use the wrong tools to solve these problems

    This is the weakest argument I've ever heard coming from the authors of a turing-complete meta language with a tail-optimized recursion depth of 1000 (which can apparently be bypassed too)

    An important subset of SQL is clearly expressible within these limitations. I'm not talking about input safety, we have databases for that. I'm talking about typing

    let res = await sql`SELECT
        ${Table['*']},
        ${Other['url']}
    FROM ${Table} t
    LEFT JOIN ${Other} o
    ON t.noneya = o.beeswax`

    ... to Table | Pick<Other, 'url'>

    Instead we get to work with ORMs defining their own javascript DSL that's somehow both leaky and incomplete, because those must be the right tool to solve these problems

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

      Issue actions