Skip to content

Design Meeting Notes, 1/3/2024 #56942

Closed
Closed
@DanielRosenwasser

Description

@DanielRosenwasser

Intersections and Unions that Depend on Instantiation Order

#56906

type Union<A, B> = A | B
type UnionAny<T> = Union<T, any>
type UnionUnknown<T> = Union<unknown, T>

// these should all be the same type but are respectively: any, any, unknown 
type UA0 = Union<unknown, any>
//   ^?
//   any
type UA1 = UnionAny<unknown>
//   ^?
//   any
type UA2 = UnionUnknown<any>
//   ^?
//   unknown
  • any "eats" everything.
  • unknown does too, except for any.
    • Problem is that we say SomeUninstantiatedTypeVariable | unknown, we eagerly reduce that to unknown.
    • We would need to defer union reduction in the presence of generics and unknown or any.
      • That would be very annoying - you wouldn't be able to say that unioning with the top type(s) just reduces to the top type.
      • [[Editor note]]: probably also have to thread through contexts where you have to know when to reduce the type.
  • It certainly is an inconsistency, but did it come up in a practical context?
    • That is unclear.
  • Conclusion: we admit that it is inconsistent and undesirable, but we are not fully convinced that it needs to change.

Disallowing More Property Access Operations on never-Typed Expressions

#56780

  • From a purely type-theoretic standpoint, there is no issue with accessing a property on never, the issue is arguably more that the code is unreachable.

  • Could argue that you should say "this whole block is unreachable, so why do you have code?"

  • That is too heavy-handed - the following doesn't deserve an error, does it?

    function foo(x: "a" | "b" | "c"): string {
      switch (x) {
        case "a":
        case "b":
        case "c":
          return "hello";
        default:
          throw Debug.fail("Wrong value " + x);
      }
    }
  • Two common views of never are

    • "Consuming" a value of type never to enforce an exhaustiveness check.
    • "Probing" a value of type never to report a helpful error for a violation of the type system or usage from untyped code.
  • In practice, this change actually breaks a bunch of existing code.

  • Conclusion: feels like we don't have an appetite

Preserving Type Refinements Within Closures Past Their Last Assignments

#56908

  • Fixes lots of issues that get duped to Trade-offs in Control Flow Analysis #9998.

  • When a closure is created after all assignments to some closed-over variable, then you can effectively think of that variable as unchanging.

  • Effectively safe to preserve the type-assignments so long as the closure isn't hoisted.

    • Means things like object methods, function expressions, and class expressions can observe prior type narrowing/refinment.
    • In theory, class declarations could also be made to work here.
      • We noticed that class declarations were considered "hoisted"...but they're not!
  • Note that we don't quite narrow in a lexical manner within "compound statements". We use the "end" of the compound statement as the source of the last assignment.

    function f5(condition: boolean) {
        let x: number | undefined;
        if (condition) {
            x = 1;
            action(() => x /*number | undefined*/)
        }
        else {
            x = 2;
            action(() => x /*number | undefined*/)
        } // <- we consider this to be the position of the last assignment of 'x'
        action(() => x /*number*/)
    }
    • This approach lends to a fast and efficient check.
    • It would also be tricky in the case of loops, where the "last" assignment cannot truly be determined.
  • Variables that are referenced in closures past the last assignment do not necessarily trigger an implicit any warning anymore.

    let x;
    
    x = 42;
    doSomething(() => x));
    //                ~
    // Implicit any error! ❌
    
    x = "hello!";
    doSomething(() => x));
    // No error! ✅
  • If we limit to let and parameters, then we don't have any issues with our perf suite - but it does for var on monaco.

    • We might have other analyses we can apply.
  • Function declarations in block scopes should maybe have captured variables appropriately narrowed.

    function f(x: string | number) {
        if (typeof x === "number") {
            function g() {
                x // should this be number, but is not in the PR
            }
            return g;
        }
    }

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