Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Design Meeting Notes, 1/6/2023 #52141

Closed
DanielRosenwasser opened this issue Jan 7, 2023 · 0 comments
Closed

Design Meeting Notes, 1/6/2023 #52141

DanielRosenwasser opened this issue Jan 7, 2023 · 0 comments
Labels
Design Notes Notes from our design meetings

Comments

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Jan 7, 2023

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Notes Notes from our design meetings
Projects
None yet
Development

No branches or pull requests

2 participants