Skip to content

Design Meeting Notes, 1/18/2023 #52296

Closed
Closed
@DanielRosenwasser

Description

@DanielRosenwasser

newLine and forceConsistentFileNames Defaults

#51909

  • newLine to lf?
    • Didn't get to discuss it thoroughly.
  • Change default of forceConsistentFileNames to true?
    • Catch issues between Windows/Mac/Linux ahead of pushing out to CI.
    • We think it's good to turn on.
  • We all feel okay about the fact that it doesn't "do what people think", right?
    • "What?"
    • It doesn't check whether the paths match what's on disk, it just checks whether the paths are consistent across the project.
  • Deprecate it?
    • Prefer not to.
  • Conclusion
    • --newLine lf by default.
    • --forceConsistentFileNames true by default.

catch Clause Variables

#52240

// @useUnknownInCatchVariables: false

try {} catch ({ e }) {}
//              ^?

try {} catch ({ e }: any) {}
//              ^?

try {} catch ({ e }: unknown) {}
//              ^?


try {} catch ({ x: { e } }) {}
//                   ^?

try {} catch ({ x: { e } }: any) {}
//                   ^?

try {} catch ({ x: { e } }: unknown) {}
//                   ^?

Comparison Operators

#52036
#52048

  • Some obvious but questionable things that popped up.
    • Number < number now disallowed
    • string | number < number now disallowed
    • unknown < number now disallowed
  • Some stuff that fails due to control flow limitations across functions.
  • What about objects that coerce to number (e.g. Date)?
    • Why don't we look at valueOf?
      • Or [Symbol.toPrimitive]?
      • Here we go...
  • Conclusion: Feels like this PR is a good start. Want to bring this in for 5.0.
    • Watch for 5.0 Beta feedback.

JSON.stringify

#51897
#52250

  • This thing broke some code.
  • Technically stringify can return undefined.
    • But technically also wrong because a Function can have a toJSON method.
  • There's a world where we could possibly keep this as-is if we reordered the signatures so that never works.
    • Overload resolution goes through a subtype pass.
  • But why are we adding this overload? Who ends up in a situation like this?
    • Doesn't even catch mistakes like (() => string) | string today.
    • Only contrived cases are caught.
  • The idea that everyone has to suffer for stringify because of these contrived-but-uncommon cases feels unreasonable.
  • Could ship an open-ended union?
  • Could also just tell people to interface-merge.
  • Conclusion:
    • Revert this, tell people to interface-merge on JSON with something that returns undefined.

Picking Between Types from Type Predicates and Original Types

#50916

  • Made a change a while back to pick the types from type predicates/type assertions.
  • Similar behavior with instanceof too.
  • Means that the original type does not survive at joining points of CFA.
  • Idea
    • In the true branch, we will still prefer the type predicate type.
    • In the false branch, we will now preserve the original type so that they join back at a CFA merge node.
  • So what do we now do about mutual subtypes?
    • For example, unknown and any are mutual subtypes.
    • Today we choose the type with the lowest ID.
    • Pretty unfortunate - these have measurable effects.
    • So we are making any a strict supertype of unknown.
      • Behavior directly in unions is the same.
      • Where else does this appear?
        • When the type IDs are not compared directly.

        • So for e.g. Array<any> vs. Array<unknown> - but Array is not special.

          type Box<T> = { readonly value: T };
          
          declare let anyBox: Box<any>;
          declare let unknownBox: Box<unknown>;
          
          let boxes1 = [anyBox, unknownBox];
          //  ^?
          let boxes2 = [unknownBox, anyBox];
          //  ^?
        • Now you always end up with Box<any>[].

    • Similar constraints with {} and any non-empty object type.
  • What other effects does this have?
    • The type of an assignment expression is always the right side:

      function f(obj: { a?: string }) {
          (obj = {}).a; // allowed?
      }
    • Hmm...

      function f(a: { x?: number }, b: { x?: string}) {
          a = b = {}; // allowed?
          // Equivalent to `a = (b = {})` which naively looks like `b = {}; a = {}` from a type perspective.
      
          a.x = 42;
      
          b.x?.toUpperCase();
      }
      • Fresh {} object type has "inverted" subtype behavior.
        • It is, but on the assignment we widen.
        • Do we have to widen? Could choose not to.
        • Feels like for the b = {}, we have to propagate out some information, or further narrow based on the left side(?)
    • (...args: any[]) => any should be a super-type of all functions?

      • But what about (...args: never[]) => unknown?
        • Not specially understood today by TS internals.

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