Skip to content

Design Meeting Notes, 2/27/2024 #57568

Closed
Closed
@DanielRosenwasser

Description

@DanielRosenwasser

Triple-Slash Reference Emit/Preservation (motivated by --isolatedDeclarations)

#57440

  • One stance we had with --isolatedDeclarations is that the flag should not affect emit itself.
  • One question around this: what do we do with /// <reference > directives?
  • When doing declaration file emit, TypeScript will try to insert
  • Let's say you have a statement like export * as fs from "fs".
    • TypeScript will emit a triple-slash reference to say /// <reference types="node" />
  • One idea for --isolatedDeclarations is to say that when TypeScript would do this, it should emit an error. In other words, the user has to write it out explicitly.
  • Problem with this is that TypeScript occasionally removes these directives as well when they're not needed.
  • This all feels pretty complicated.
  • Suggestion: preserve triple-slash references when they're written, never generate them.
    • Thinking we could do this in 6.0.
  • The other option would be an attribute on /// references that allows people to control whether the reference is preserved, elided, or whatever.
  • Do we know how often we generate these in the top200 repos?
    • No
  • Until recently, preservation wasn't perfect anyway - so maybe people aren't relying on it?
  • One strong preference: what you see is what you get.
  • If you inserted one of these references, you had to make sure you had the right types installed. The difference is a UX issue of what makes the error more obvious.
  • Can view /// refs as a holdover from a time long-past. Don't ascribe them more value than they deserve!
  • What about the non-types references?
    • /// <reference types="..." />
    • /// <reference paths="..." />
  • Do we synthesize?
    • types: currently sometimes yes.
    • paths (aka file references): hopefully no?
  • Do we elide?
    • types: currently also sometimes yes.
    • paths (aka file references): hopefully no?
  • Do we generate /// <reference paths="..." />
  • So we want to make these changes. But when?
    • One idea floated was to make sure that this only kicks in within --isolatedDeclarations, and gets turned on for everyone in 6.0
  • If we're not going to synthesize references, it might be appropriate to tell library authors that they need to preserve that?
  • A little gun-shy on including this in 5.5.
    • --verbatimReferenceDirectives, require this in --isolatedDeclarations, change it in 6.0...
      • And then deprecate --verbatimReferenceDirectives in 6.0? 😂
    • We'd like to see how common this is.
  • Nightly?
    • Nobody is going to publish on nightly!
  • Conclusion: Let's try to create an error when synthesizing, see what breaks on the top 400 repos, and then decide if we need a new flag.

Inferring Type Predicate Signatures from Function Bodies

#57465

  • Motivating example.

    const arr = [1, 2, "a", "b", undefined];
    
    const numbers = arr.filter(x => typeof x === "number");
    const el1: number = numbers[0]
  • Today that errors because the filter doesn't work.

  • Solution is to explicitly make the signature a type predicate

    - const numbers = arr.filter(x => typeof x === "number");
    + const numbers = arr.filter((x): x is number => typeof x === "number");
  • PR makes it so these signatures can be automatically infer.

    • Functions
      • with no explicit return type
      • that return a boolean
      • with a single return expression.
  • Analysis cost?

    • Most repos had no perf cost.
    • But occasionally there was a cost.
      • Cost to detect the guards.
      • Cost to deal with the usage of type guards.
    • In experiments, most of the cost comes from detecting if functions are type guards.
    • More from the usage in type guards.
  • This has been a long-standing request - people really don't like that filter doesn't work.

  • What happens when multiple variables get narrowed?

    • Seems like it's a bail-out.
    • Good, that's what we want.
      • Also, the false branch of a (x is number AND y is number) can't guarantee that the variables aren't both number. Kind of hard to reason about.
  • What broke?

    • Inability to express comparability bounds:

      declare const cat: Cat;
      declare const arr: Animal[];
      
      const dogs = arr.filter(a => isDog(a));
      
      dogs.indexOf(cat);
    • Inlining in Pyright.

      • Notably, not a filter!
  • Could argue that so many libraries that have their own isNotNull etc. exist only because function expressions can't narrow.

  • In some ways this is way better because type predicates are not checked based on how you've narrowed! But inferring is actually way better!

    • ...should we make sure that they are?
    • We would like to. Gotta find a checking opt-out.
  • What are the downsides?

    • Lots of expressions simply won't get a type guard inferred.
      • "What about !!x?"
        • No helper for Truthy.

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