Skip to content

Design Meeting Notes, 1/6/2023 #52141

@DanielRosenwasser

Description

@DanielRosenwasser

Stricter Relational Comparison Operators

#52036
#52048

  • When narrowed to Promise, we reject comparisons with a number.
  • We use the comparable relationship to determine if we can compare two types. It is optimistic and roughly checks if one operand has overlap with the other - and allows a Promise | number to be compared with a number.
  • Recurring issue - discussed in the past
  • Is it meaningful to allow >= when you have an object?
    • Feels like it's questionable.
  • Can you get away with just restricting to numbers and bigints?
    • strings!
  • "We have to be more choosier when checking for overlappiness"
  • Kind of want to figure out a strategy where we get the base primitive type of each operand and then check if they're assignable to bigint | number, or string, and both have overlap.
    • Probably some subtlety - make sure to test with generics.

Improved Logic When Choosing Between Covariant and Contravariant Inferences

#52111 (comment)
#52123

interface A { a: string }
interface B extends A { b: string }
interface C extends A { c: string }

declare function cast<T, U extends T>(x: T, test: (x: T) => x is U): U;

declare function isC(x: A): x is C;

function f1(a: A, b: B) {
    const x1 = cast(a, isC);  // cast<A, C>
    const x2 = cast(b, isC);  // cast<A, C>
    //                 ~~~
    // Argument of type '(x: A) => x is C' is not assignable to parameter of type '(x: B) => x is B'.
    //   Type predicate 'x is C' is not assignable to 'x is B'.
    //     Property 'b' is missing in type 'C' but required in type 'B'.
}
  • When doing type argument inference, we separate covariant inference sites from contravariant inference sites.
    • When deciding on what to infer, we use these to prioritize.
    • We try to pick covariant inferences, and by default construct a type from covariant inference candidates.
      • We do not use this type constructed by covariant inferences if:
        • the constructed type is never, or
        • it's not a subtype of any contravariant inference candidate.
      • [Editor's Note]: The intuition here is that a contravariant inference site places a hard expectation on what it can be given; if that fails, using the contravariant inference will provide a better error message.
    • Improve logic that chooses co- vs. contra-variant inferences #52123 added another exception here where we do not do this when the type parameter is used as a constraint of another type parameter and for that constrained type parameter, its covariant inference candidates the constructed covariant type of its constraint (i.e. the original type parameter).
    • So now in the assignment to x2, we choose the type A instead of C because T is used as the constraint of U, and the candidate of A would not have been assignable to
  • When a covariant inference is a subtype of the contravariant inference, you have a range of types you can choose from.
  • Doesn't solve all possible variations of this - doesn't fix the case of arbitrarily nested generic constraints.

Varying Subtype Reduction Depending on Declaration Order

#52100

declare let u: Promise<unknown>
declare let a: Promise<any>

// Assigned an expression that is Promise<any> | Promise<unknown>
// but that will undergo subtype reduction
// The type of union depends on whether a or u was declared first
let union =
//  ^?
// Varies between 'Promise<any>' and 'Promise<unknown>'.
    Math.random() > 0.5
        ? Promise.reject<any>()
        : Promise.reject<unknown>();

union.then(v => v.toString());
  • These differ by type ID based on where they're declared in the file and get sorted in an initial union type.
  • When we try to reduce the union, whichever came first "wins" because any and unknown are both (non-strict) subtypes of each other.
  • Unclear if you'd want unknown or any to win out. Presumably first principles would indicate unknown because it's the safer one.
  • Don't really have any solution here. Many similar issues show up.

Monomorphism Recap - Runtime Speed vs. Memory Usage

#51682
#51880

  • We got some nice performance boosts from ensuring definite shapes on certain Nodes and Symbols - but it came at the expense of memory.
  • Feels like a good tradeoff in general.
  • We can start shrinking many of the types we have in the compiler - lots of optional boolean properties.
    • But V8 allocates in chunks - so this work could be for nothing.
    • Still buys us some wiggle room, more possibility to shrink down the line.
  • Can we just shrink down Identifier to 2 properties?
    • 2 additional properties on top of Node.
    • We can try.
    • Get rid of originalKeywordKind
    • Move hasExtendedUnicodeEscape to the containing source file, turn it into a slow path during emit.
    • Move emit-related things into their own bucket.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design NotesNotes from our design meetings

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions