Skip to content

Fix #55632 issue (No type inferrence of callback arguments inside a tuple union type) #60434

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

fwolff
Copy link

@fwolff fwolff commented Nov 6, 2024

Passes all existing test cases and Airtable codebase ("Identicality" checks are probably quite rare so unsurprising). Creating this in the hopes that we can use the test infra on this repo to run this against the corpus of other open source libraries.

  • There is an associated issue in the Backlog milestone (required)
  • Code is up-to-date with the main branch
  • You've successfully run hereby runtests locally
  • There are new or updated unit tests validating the change

Fixes #55632.

In summary, the PR adds a new method:

function discriminateContextualTypeByArrayElements(node: ArrayLiteralExpression, contextualType: UnionType) {
    const key = `D${getNodeId(node)},${getTypeId(contextualType)}`;
    const cachedType = getCachedType(key);
    if (cachedType)
        return cachedType;

    const elementsLength = node.elements.length;
    const filteredType = filterType(contextualType, type => {
        if (!isTupleLikeType(type))
            return true;
        if (isTupleType(type))
            return elementsLength >= type.target.minLength && (!!(type.target.combinedFlags & ElementFlags.Variable) || elementsLength <= type.target.fixedLength);
        const properties = getPropertiesOfType(type);
        if (elementsLength > properties.length || elementsLength < properties.reduce((c, p) => p.flags & SymbolFlags.Optional ? c : ++c, 0))
            return false;
        for (let i = 0; i < elementsLength; i++) {
            if (!some(properties, p => p.escapedName === ("" + i) as __String))
                return false;
        }
        return true;
    });

    if (filteredType.flags & TypeFlags.Never)
        return setCachedType(key, contextualType);
    if (!(filteredType.flags & TypeFlags.Union))
        return setCachedType(key, isTupleLikeType(filteredType) ? filteredType : contextualType);

    return setCachedType(key, discriminateTypeByDiscriminableItems(
        filteredType as UnionType,
        node.elements.map((element, index) => {
            const name = ("" + index) as __String
            return isPossiblyDiscriminantValue(element) && isDiscriminantProperty(filteredType, name) ?
                [() => getContextFreeTypeOfExpression(element), name] as const :
                undefined
        }).filter(discriminator => !!discriminator),
        isTypeAssignableTo,
    ));
}

...and calls it in the existing getApparentTypeOfContextualType method:

if (apparentType.flags & TypeFlags.Union) {
    if (isObjectLiteralExpression(node))
        return discriminateContextualTypeByObjectMembers(node, apparentType as UnionType);
    if (isJsxAttributes(node))
        return discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType);
// -------
    if (isArrayLiteralExpression(node))
        return discriminateContextualTypeByArrayElements(node, apparentType as UnionType);
// -------
}
return apparentType;

It also adds a new unit tests file tests/cases/compiler/tupleTypeInference3.ts with the code failing in #55632 and other related tests.

The PR doesn't provide anything for code completion, which could be handled by #58571.

Question: I use the isTupleLikeType method to match actual tuple types and tuple-like types like type T = { 0: 1, 1: (a: boolean) => void } (see the new unit tests file). The isTupleLikeType checks also for array-like type, but I was unable to create any such type. Can anybody tell me how to create such types and what I should do with them?

@typescript-bot typescript-bot added the For Backlog Bug PRs that fix a backlog bug label Nov 6, 2024
@fwolff
Copy link
Author

fwolff commented Nov 6, 2024

@microsoft-github-policy-service agree

@fwolff
Copy link
Author

fwolff commented Nov 27, 2024

@Andarist @jcalz any feedback (even negative) would be greatly appreciated:

Thanks


const elementsLength = node.elements.length;
const filteredType = filterType(contextualType, type => {
if (!isTupleLikeType(type)) return true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this isn't particularly practical, I think you can't use isTupleLikeType here. What about { 1: (a: number) => void } contributing to the contextual type?

function test(arg: { 1: (arg: number) => void }) {}
test([null, (arg) => {}] as const);

Basically all types should be consulted for exact property matches and number index signatures.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But ofc those tuples that can't be matched (like the tuples that are guaranteed too short for the given array literal expression) can be filtered out.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't use isTupleLikeType anymore and check for types that have only integer properties (or "length"). I added a new test with your { 1: (arg: number) => void } type.

@@ -32029,6 +32029,52 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
);
}

function areTupleLikeProperties(properties: Symbol[]): boolean {
return properties.length > 0 && every(properties, p => p.escapedName === "length" as __String || /^\d+$/.test(p.escapedName.toString()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this won't work in a situation like this:

function test(arg: { 1: (arg: number) => void; foo?: string }) {}
test([null, (arg) => {}] as const);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, if I'm not mistaken this won't work either:

function test(arg: { 1: (arg: number) => void; [k: number]: (...args: never) => void }) {}
test([() => {}, (arg) => {}] as const);

I'd try to avoid ignoring a type if it can't be proven to be incompatible. It should be easier to land on a more correct solution this way

Copy link
Author

@fwolff fwolff Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed the code so it works now with:

function test(arg: { 1: (arg: number) => void; foo?: string }) {}
test([null, (arg) => {}] as const);

I only discard types (prior to the discriminateTypeByDiscriminableItems call) if:

  • They are true tuple types with an incompatible length,
  • They are types with at least a non optional property key of the integer form which is out of range (negative or >= of the array value length).

So, now, this is ok:

declare function f2(_: { 0: number, 1: (arg: number) => void } | { 0: number, 1: (arg: boolean) => void, 2: any }): void;
f2([1, arg => { arg === 0 }] as const);

Two questions:

  1. I use /^[-+]?\d+$/ to match index like property keys (so { "-1": boolean } is discarded because it has a negative index). Is it correct?

  2. { 1: (arg: number) => void } | [1, (arg: boolean) => void] leads to an error with [1, (arg) => { }] (Parameter 'arg' implicitly has an 'any' type), because discriminateTypeByDiscriminableItems fails to discard the first type of the union. I should fix that as well, no?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use /^[-+]?\d+$/ to match index like property keys (so { "-1": boolean } is discarded because it has a negative index). Is it correct?

I guess it could be discarded if that property is required. It shouldn't be discarded if it's optional though. But I'm not sure if it's really worth discarding them - if somebody wants to write a union type with such a thing and hope for something to be discarded... they should rethink their choices 😅

{ 1: (arg: number) => void } | [1, (arg: boolean) => void] leads to an error with [1, (arg) => { }] (Parameter 'arg' implicitly has an 'any' type), because discriminateTypeByDiscriminableItems fails to discard the first type of the union. I should fix that as well, no?

No, this is correct. Both of those types can provide the type for (arg) => {}. So the implicit any is the correct result here.

Comment on lines 77 to 81
// declare function f5(arg: { 1: (arg: number) => void; foo?: string } | [1, (arg: boolean) => void]): void;
// f5([null, (arg) => { arg === 0 }] as const);

// declare function f6(arg: { 1: (arg: number) => void; [k: number]: (...args: never) => void } | [1, (arg: boolean) => void]): void;
// f6([() => {}, (arg) => { arg === 0 }] as const);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps u meant to uncomment those?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is part of my problem. This test issues an error (Parameter 'arg' implicitly has an 'any' type):

declare function f5(arg: { 1: (arg: number) => void } | [1, (arg: boolean) => void]): void;
f5([1, (arg) => { arg === true }] as const);

I don't known what to do about this error because both types in { 1: (arg: number) => void } | [1, (arg: boolean) => void] are possible matches for [1, (arg) => { }]. But [1, (arg: boolean) => void] seems to be a better match. So, is the compiler meant to find the best possible match or only discard incompatible types?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't known what to do about this error because both types in { 1: (arg: number) => void } | [1, (arg: boolean) => void] are possible matches for [1, (arg) => { }]. But [1, (arg: boolean) => void] seems to be a better match. So, is the compiler meant to find the best possible match or only discard incompatible types?

To the best of my knowledge, you should return both and this should result in an implicit any because the (arg: boolean) => void shouldn't be treated as "a better match". I distinctly remember when I sent out a PR that was too eager about discarding those tuple like types, it got merged and then reverted :P

Copy link
Author

@fwolff fwolff Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this is good news ;) What about this one:

declare function f6(arg: { 1: (arg: number) => void; [k: number]: (...args: never) => void } | [1, (arg: boolean) => void]): void;
f6([() => {}, (arg) => { }] as const);

It fails with a Parameter 'arg' implicitly has an 'any' type error, because () => {} isn't a possible discriminant value (isPossiblyDiscriminantValue(element) returns false). But this is a weird because () => {} shouldn't match 1.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When in doubt, check how objects work for the equivalent case:

declare function f7(
  arg:
    | { foo: (arg: number) => void; [k: string]: (...args: never) => void }
    | { bar: 1; foo: (arg: boolean) => void },
): void;
f7({
  bar: () => {},
  foo: (arg) => {}, // implicit any
});

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for your help and advices!

I just pushed a new commit with few additional tests. Can I consider that this PR could be accepted as is? What do I need to do next?

@fwolff
Copy link
Author

fwolff commented Jan 7, 2025

Hi @Andarist. Any chance to get this PR merged in a foreseeable future? Tell me if I need to do anything else.

@fwolff
Copy link
Author

fwolff commented Feb 12, 2025

@Andarist a kind reminder: what's the point of asking for help if solutions aren't merged?

@jakebailey
Copy link
Member

@fwolff they're an external contributor; there's no need for that kind of commentary.

@typescript-bot test it
@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Feb 12, 2025

Starting jobs; this comment will be updated as builds start and complete.

Command Status Results
pack this ✅ Started ✅ Results
test top400 ✅ Started ✅ Results
user test this ✅ Started 👀 Results
run dt ✅ Started 👀 Results
perf test this faster ✅ Started 👀 Results

@typescript-bot
Copy link
Collaborator

typescript-bot commented Feb 12, 2025

Hey @jakebailey, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/164771/artifacts?artifactName=tgz&fileId=7E44BB9ED855F703F3BD4D0DA7D2DBA405CD5522567D6271DC0002C9468FED4F02&fileName=/typescript-5.8.0-insiders.20250212.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/pr-build@5.8.0-pr-60434-6".;

@typescript-bot
Copy link
Collaborator

Hey @jakebailey, the results of running the DT tests are ready.

There were interesting changes:

Branch only errors:

Package: frida-gum
Error:

Error: 
/mnt/vss/_work/1/DefinitelyTyped/types/frida-gum/frida-gum-tests.ts
  126:1  error  TypeScript@local expected type to be:
  NativeFunction<[number, number], [number | Int64, [number, [NativePointerValue, NativePointerValue]]]>
got:
  NativeFunction<number[], [number | Int64, (number | NativePointerValue[])[]]>  @definitelytyped/expect
  128:1  error  TypeScript@local expected type to be:
  [number, number]
got:
  number[]                                                                                                                                                             @definitelytyped/expect
  130:1  error  TypeScript@local expected type to be:
  [number, number]
got:
  number[]                                                                                                                                                             @definitelytyped/expect

✖ 3 problems (3 errors, 0 warnings)

    at combineErrorsAndWarnings (/mnt/vss/_work/1/DefinitelyTyped/node_modules/.pnpm/@definitelytyped+dtslint@0.2.28_typescript@5.8.0-dev.20250212/node_modules/@definitelytyped/dtslint/dist/index.js:194:28)
    at runTests (/mnt/vss/_work/1/DefinitelyTyped/node_modules/.pnpm/@definitelytyped+dtslint@0.2.28_typescript@5.8.0-dev.20250212/node_modules/@definitelytyped/dtslint/dist/index.js:186:20)

Package: ffi-napi
Error:

Error: 
/mnt/vss/_work/1/DefinitelyTyped/types/ffi-napi/ffi-napi-tests.ts
   24:45  error  TypeScript@local compile error: 
Argument of type 'Pointer<Pointer<void>>' is not assignable to parameter of type 'string | Type<Pointer<Pointer<void>>>'.
  Type 'Pointer<Pointer<void>>' is missing the following properties from type 'Type<Pointer<Pointer<void>>>': size, indirection, get                                                    @definitelytyped/expect
  121:1   error  TypeScript@local expected type to be:
  ForeignFunction<void, []> | VariadicForeignFunction<CoerceType<"void">, []> | ((args_0: (err: any, value: void) => void) => void)
got:
  ForeignFunction<unknown, never[]> | VariadicForeignFunction<CoerceType<string>, never[]> | ((...args: [...never[], (err: any, value: unknown) => void]) => void)  @definitelytyped/expect
  123:1   error  TypeScript@local expected type to be:
  ForeignFunction<void, []>
got:
  ForeignFunction<unknown, never[]>                                                                                                                                                                                                                                         @definitelytyped/expect
  125:1   error  TypeScript@local expected type to be:
  ForeignFunction<void, []>
got:
  ForeignFunction<unknown, never[]>                                                                                                                                                                                                                                         @definitelytyped/expect
  127:1   error  TypeScript@local expected type to be:
  ForeignFunction<void, []>
got:
  ForeignFunction<unknown, never[]>                                                                                                                                                                                                                                         @definitelytyped/expect
  129:1   error  TypeScript@local expected type to be:
  ForeignFunction<void, []>
got:
  ForeignFunction<unknown, never[]>                                                                                                                                                                                                                                         @definitelytyped/expect
  131:1   error  TypeScript@local expected type to be:
  ForeignFunction<void, []>
got:
  ForeignFunction<unknown, never[]>                                                                                                                                                                                                                                         @definitelytyped/expect
  133:1   error  TypeScript@local expected type to be:
  VariadicForeignFunction<CoerceType<"void">, []>
got:
  VariadicForeignFunction<CoerceType<string>, never[]>                                                                                                                                                                                                @definitelytyped/expect
  135:1   error  TypeScript@local expected type to be:
  (args_0: (err: any, value: void) => void) => void
got:
  (...args: [...never[], (err: any, value: unknown) => void]) => void                                                                                                                                                                               @definitelytyped/expect

✖ 9 problems (9 errors, 0 warnings)

    at combineErrorsAndWarnings (/mnt/vss/_work/1/DefinitelyTyped/node_modules/.pnpm/@definitelytyped+dtslint@0.2.28_typescript@5.8.0-dev.20250212/node_modules/@definitelytyped/dtslint/dist/index.js:194:28)
    at runTests (/mnt/vss/_work/1/DefinitelyTyped/node_modules/.pnpm/@definitelytyped+dtslint@0.2.28_typescript@5.8.0-dev.20250212/node_modules/@definitelytyped/dtslint/dist/index.js:186:20)

Package: ref-struct-di
Error:

Error: 
/mnt/vss/_work/1/DefinitelyTyped/types/ref-struct-di/ref-struct-di-tests.ts
  38:1  error  TypeScript@local expected type to be:
  StructType<{ x: Type<any>; }>
got:
  StructType<{ [x: string]: Type<any>; }>         @definitelytyped/expect
  40:1  error  TypeScript@local expected type to be:
  StructType<{ x: Type<any>; }>
got:
  StructType<{ [x: string]: Type<any>; }>         @definitelytyped/expect
  42:1  error  TypeScript@local expected type to be:
  StructType<{ x: Type<any>; }>
got:
  StructType<{ [x: string]: Type<any>; }>         @definitelytyped/expect
  44:1  error  TypeScript@local expected type to be:
  StructType<{ x: Type<number>; }>
got:
  StructType<{ [x: string]: Type<number>; }>   @definitelytyped/expect
  46:1  error  TypeScript@local expected type to be:
  StructType<{ x: Type<number>; }>
got:
  StructType<{ [x: string]: Type<unknown>; }>  @definitelytyped/expect
  65:1  error  TypeScript@local expected type to be:
  StructType<{ x: Type<any>; }>
got:
  StructType<{ [x: string]: Type<any>; }>         @definitelytyped/expect
  67:1  error  TypeScript@local expected type to be:
  StructType<{ x: Type<any>; }>
got:
  StructType<{ [x: string]: Type<any>; }>         @definitelytyped/expect
  69:1  error  TypeScript@local expected type to be:
  StructType<{ x: Type<any>; }>
got:
  StructType<{ [x: string]: Type<any>; }>         @definitelytyped/expect
  71:1  error  TypeScript@local expected type to be:
  StructType<{ x: Type<number>; }>
got:
  StructType<{ [x: string]: Type<number>; }>   @definitelytyped/expect
  73:1  error  TypeScript@local expected type to be:
  StructType<{ x: Type<number>; }>
got:
  StructType<{ [x: string]: Type<unknown>; }>  @definitelytyped/expect

✖ 10 problems (10 errors, 0 warnings)

    at combineErrorsAndWarnings (/mnt/vss/_work/1/DefinitelyTyped/node_modules/.pnpm/@definitelytyped+dtslint@0.2.28_typescript@5.8.0-dev.20250212/node_modules/@definitelytyped/dtslint/dist/index.js:194:28)
    at runTests (/mnt/vss/_work/1/DefinitelyTyped/node_modules/.pnpm/@definitelytyped+dtslint@0.2.28_typescript@5.8.0-dev.20250212/node_modules/@definitelytyped/dtslint/dist/index.js:186:20)

You can check the log here.

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the user tests with tsc comparing main and refs/pull/60434/merge:

Something interesting changed - please have a look.

Details

effect

packages/effect/benchmark/tsconfig.json

tsconfig.json

tsconfig.build.json

tsconfig.base.json

packages/effect/dtslint/tsconfig.json

@typescript-bot
Copy link
Collaborator

@jakebailey
The results of the perf run you requested are in!

Here they are:

tsc

Comparison Report - baseline..pr
Metric baseline pr Delta Best Worst p-value
Compiler-Unions - node (v18.15.0, x64)
Errors 34 34 ~ ~ ~ p=1.000 n=6
Symbols 62,390 62,392 +2 (+ 0.00%) ~ ~ p=0.001 n=6
Types 50,395 50,397 +2 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 194,701k (± 1.02%) 194,830k (± 0.98%) ~ 192,938k 196,623k p=0.173 n=6
Parse Time 1.30s (± 1.02%) 1.30s (± 1.41%) ~ 1.27s 1.32s p=0.605 n=6
Bind Time 0.73s 0.73s ~ ~ ~ p=1.000 n=6
Check Time 9.75s (± 0.12%) 9.77s (± 0.58%) ~ 9.69s 9.83s p=0.374 n=6
Emit Time 2.72s (± 0.72%) 2.74s (± 0.55%) ~ 2.72s 2.76s p=0.073 n=6
Total Time 14.50s (± 0.10%) 14.55s (± 0.43%) ~ 14.43s 14.60s p=0.077 n=6
angular-1 - node (v18.15.0, x64)
Errors 37 37 ~ ~ ~ p=1.000 n=6
Symbols 948,488 948,818 +330 (+ 0.03%) ~ ~ p=0.001 n=6
Types 411,006 411,097 +91 (+ 0.02%) ~ ~ p=0.001 n=6
Memory used 1,224,162k (± 0.00%) 1,225,259k (± 0.00%) +1,097k (+ 0.09%) 1,225,203k 1,225,358k p=0.005 n=6
Parse Time 6.65s (± 0.65%) 6.67s (± 0.51%) ~ 6.60s 6.69s p=0.413 n=6
Bind Time 1.89s (± 0.80%) 1.88s (± 0.64%) ~ 1.87s 1.90s p=0.677 n=6
Check Time 31.76s (± 0.38%) 32.09s (± 0.24%) +0.33s (+ 1.04%) 32.00s 32.18s p=0.005 n=6
Emit Time 15.22s (± 0.59%) 15.24s (± 0.59%) ~ 15.17s 15.42s p=0.747 n=6
Total Time 55.52s (± 0.31%) 55.89s (± 0.16%) +0.37s (+ 0.67%) 55.79s 56.00s p=0.013 n=6
mui-docs - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 2,372,158 2,372,234 +76 (+ 0.00%) ~ ~ p=0.001 n=6
Types 846,197 846,244 +47 (+ 0.01%) ~ ~ p=0.001 n=6
Memory used 2,134,719k (± 0.00%) 2,134,751k (± 0.00%) ~ 2,134,671k 2,134,925k p=0.689 n=6
Parse Time 7.26s (± 0.37%) 7.26s (± 0.22%) ~ 7.24s 7.28s p=0.685 n=6
Bind Time 2.45s (± 0.33%) 2.47s (± 0.55%) ~ 2.45s 2.49s p=0.078 n=6
Check Time 72.82s (± 0.14%) 72.67s (± 1.35%) ~ 70.73s 73.41s p=0.575 n=6
Emit Time 0.15s (± 2.75%) 0.15s (± 3.77%) ~ 0.14s 0.15s p=0.282 n=6
Total Time 82.69s (± 0.13%) 82.55s (± 1.17%) ~ 80.63s 83.26s p=0.521 n=6
self-build-src - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,228,511 1,228,761 +250 (+ 0.02%) ~ ~ p=0.001 n=6
Types 266,960 267,015 +55 (+ 0.02%) ~ ~ p=0.001 n=6
Memory used 2,358,385k (± 0.01%) 2,359,081k (± 0.02%) +696k (+ 0.03%) 2,358,370k 2,359,704k p=0.020 n=6
Parse Time 5.21s (± 1.31%) 5.18s (± 0.28%) ~ 5.15s 5.19s p=0.568 n=6
Bind Time 1.79s (± 1.45%) 1.78s (± 0.97%) ~ 1.76s 1.80s p=0.742 n=6
Check Time 35.22s (± 0.20%) 35.25s (± 0.48%) ~ 35.11s 35.58s p=0.936 n=6
Emit Time 2.96s (± 0.71%) 2.96s (± 1.26%) ~ 2.92s 3.02s p=0.422 n=6
Total Time 45.17s (± 0.24%) 45.18s (± 0.42%) ~ 45.02s 45.54s p=0.575 n=6
self-build-src-public-api - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 1,228,511 1,228,761 +250 (+ 0.02%) ~ ~ p=0.001 n=6
Types 266,960 267,015 +55 (+ 0.02%) ~ ~ p=0.001 n=6
Memory used 3,033,658k (± 9.75%) 2,913,885k (±12.84%) ~ 2,430,081k 3,156,190k p=0.471 n=6
Parse Time 6.98s (± 1.08%) 6.93s (± 1.80%) ~ 6.77s 7.07s p=0.688 n=6
Bind Time 2.15s (± 1.47%) 2.15s (± 1.76%) ~ 2.09s 2.19s p=0.936 n=6
Check Time 42.74s (± 0.43%) 42.79s (± 0.63%) ~ 42.37s 43.14s p=0.575 n=6
Emit Time 3.53s (± 2.17%) 3.46s (± 2.01%) ~ 3.38s 3.55s p=0.128 n=6
Total Time 55.40s (± 0.50%) 55.32s (± 0.69%) ~ 54.78s 55.81s p=0.748 n=6
self-compiler - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 262,817 262,984 +167 (+ 0.06%) ~ ~ p=0.001 n=6
Types 106,833 106,875 +42 (+ 0.04%) ~ ~ p=0.001 n=6
Memory used 440,463k (± 0.02%) 440,662k (± 0.01%) +199k (+ 0.05%) 440,553k 440,742k p=0.008 n=6
Parse Time 3.51s (± 0.34%) 3.54s (± 0.78%) ~ 3.49s 3.57s p=0.075 n=6
Bind Time 1.31s (± 0.57%) 1.31s (± 0.68%) ~ 1.30s 1.32s p=0.798 n=6
Check Time 18.90s (± 0.48%) 18.95s (± 0.47%) ~ 18.82s 19.03s p=0.630 n=6
Emit Time 1.51s (± 1.35%) 1.51s (± 1.09%) ~ 1.49s 1.53s p=0.569 n=6
Total Time 25.24s (± 0.44%) 25.30s (± 0.34%) ~ 25.18s 25.40s p=0.521 n=6
ts-pre-modules - node (v18.15.0, x64)
Errors 70 70 ~ ~ ~ p=1.000 n=6
Symbols 226,113 226,130 +17 (+ 0.01%) ~ ~ p=0.001 n=6
Types 94,488 94,499 +11 (+ 0.01%) ~ ~ p=0.001 n=6
Memory used 371,526k (± 0.05%) 371,467k (± 0.05%) ~ 371,296k 371,851k p=0.689 n=6
Parse Time 2.87s (± 1.05%) 2.89s (± 0.94%) ~ 2.86s 2.92s p=0.254 n=6
Bind Time 1.59s (± 0.93%) 1.61s (± 1.57%) ~ 1.59s 1.65s p=0.120 n=6
Check Time 16.49s (± 0.61%) 16.48s (± 0.34%) ~ 16.40s 16.57s p=0.872 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 20.95s (± 0.45%) 20.98s (± 0.40%) ~ 20.89s 21.13s p=0.336 n=6
vscode - node (v18.15.0, x64)
Errors 3 3 ~ ~ ~ p=1.000 n=6
Symbols 3,208,064 3,208,181 +117 (+ 0.00%) ~ ~ p=0.001 n=6
Types 1,088,587 1,088,633 +46 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 3,282,741k (± 0.01%) 3,283,892k (± 0.01%) +1,151k (+ 0.04%) 3,283,566k 3,284,048k p=0.005 n=6
Parse Time 14.21s (± 0.47%) 14.21s (± 0.56%) ~ 14.12s 14.30s p=0.809 n=6
Bind Time 4.59s (± 0.67%) 4.65s (± 2.62%) ~ 4.55s 4.81s p=0.571 n=6
Check Time 90.52s (± 3.38%) 89.32s (± 1.80%) ~ 88.18s 91.78s p=0.470 n=6
Emit Time 24.64s (± 8.77%) 24.45s (± 9.43%) ~ 22.80s 27.49s p=0.810 n=6
Total Time 133.95s (± 2.15%) 132.63s (± 2.15%) ~ 129.96s 137.16s p=0.689 n=6
webpack - node (v18.15.0, x64)
Errors 0 0 ~ ~ ~ p=1.000 n=6
Symbols 293,864 293,904 +40 (+ 0.01%) ~ ~ p=0.001 n=6
Types 119,613 119,616 +3 (+ 0.00%) ~ ~ p=0.001 n=6
Memory used 447,091k (± 0.03%) 447,213k (± 0.01%) ~ 447,180k 447,259k p=0.230 n=6
Parse Time 4.09s (± 1.31%) 4.09s (± 1.02%) ~ 4.04s 4.16s p=0.936 n=6
Bind Time 1.77s (± 1.31%) 1.77s (± 0.66%) ~ 1.76s 1.79s p=0.740 n=6
Check Time 18.69s (± 0.53%) 18.72s (± 0.64%) ~ 18.55s 18.89s p=0.936 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 24.55s (± 0.59%) 24.58s (± 0.49%) ~ 24.44s 24.77s p=0.810 n=6
xstate-main - node (v18.15.0, x64)
Errors 5 5 ~ ~ ~ p=1.000 n=6
Symbols 557,768 557,770 +2 (+ 0.00%) ~ ~ p=0.001 n=6
Types 186,275 186,341 +66 (+ 0.04%) ~ ~ p=0.001 n=6
Memory used 494,813k (± 0.01%) 494,931k (± 0.02%) ~ 494,790k 495,035k p=0.066 n=6
Parse Time 3.40s (± 0.58%) 3.42s (± 0.34%) ~ 3.41s 3.44s p=0.250 n=6
Bind Time 1.19s (± 0.82%) 1.20s (± 1.33%) ~ 1.18s 1.22s p=0.314 n=6
Check Time 19.46s (± 0.59%) 19.50s (± 0.37%) ~ 19.37s 19.56s p=0.378 n=6
Emit Time 0.00s 0.00s ~ ~ ~ p=1.000 n=6
Total Time 24.05s (± 0.53%) 24.12s (± 0.37%) ~ 23.96s 24.20s p=0.423 n=6
System info unknown
Hosts
  • node (v18.15.0, x64)
Scenarios
  • Compiler-Unions - node (v18.15.0, x64)
  • angular-1 - node (v18.15.0, x64)
  • mui-docs - node (v18.15.0, x64)
  • self-build-src - node (v18.15.0, x64)
  • self-build-src-public-api - node (v18.15.0, x64)
  • self-compiler - node (v18.15.0, x64)
  • ts-pre-modules - node (v18.15.0, x64)
  • vscode - node (v18.15.0, x64)
  • webpack - node (v18.15.0, x64)
  • xstate-main - node (v18.15.0, x64)
Benchmark Name Iterations
Current pr 6
Baseline baseline 6

Developer Information:

Download Benchmarks

@typescript-bot
Copy link
Collaborator

@jakebailey Here are the results of running the top 400 repos with tsc comparing main and refs/pull/60434/merge:

Everything looks good!

@fwolff
Copy link
Author

fwolff commented Feb 13, 2025

@fwolff they're an external contributor; there's no need for that kind of commentary.

Hello @jakebailey. Sorry for being impatient: @Andarist helped me a lot, I didn't want to be rude!

return !hasIndexPropertyOutOfRange(type, elementsLength);
});

if (filteredType.flags & TypeFlags.Never) return setCachedType(key, contextualType);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: why is contextualType preserved here if everything was filtered out?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There used to be an error with other tests when I returned an empty filteredType but this is no longer the case. The new code returns filteredType now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This definitely needs more tests with spread elements. You should go crazy with cases like [...other, (arg) => {}] and more

return index >= 0 || index < length;
});

const elementsLength = node.elements.length;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ties back to https://github.com/microsoft/TypeScript/pull/60434/files#r1957160962 - you can't use this as-is. This is the length of AST elements, not the length of actual elements in the array.

You might want to take a look into getContextualTypeForElementExpression and how it's using firstSpreadIndex and lastSpreadIndex. It feels like you should use a similar-ish logic here but I haven't thought about it deeply. You could also focus on discriminating only by leading non-spread elements first - that should be simpler.

Comment on lines +32037 to +32049
const isIndexPropertyLike = (property: Symbol) => /^[-+]?\d+$/.test(property.escapedName as string);
const hasIndexPropertyOutOfRange = (type: Type, length: number) =>
some(getPropertiesOfType(type), p => {
if (p.flags & SymbolFlags.Optional || !isIndexPropertyLike(p)) return false;
const index = +p.escapedName;
return index < 0 || index >= length;
});
const hasIndexPropertyInRange = (type: Type, length: number) =>
some(getPropertiesOfType(type), p => {
if (!isIndexPropertyLike(p)) return false;
const index = +p.escapedName;
return index >= 0 || index < length;
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thiiink those are mostly redundant. An algorithm matching closer discriminateContextualTypeByObjectMembers would likely suffice here. You could create a list of potential discriminators based on the leading non-spread elements and pass that to discriminateTypeByDiscriminableItems. Then you might apply one extra filter after it (or before it?) to handle tuple-specific cases based on .fixedLength and similar

@Andarist
Copy link
Contributor

@jakebailey I don't mind - all is good. I can echo some of that @fwolff's sentiment though. Maybe some note should be added to the CONTRIBUTING doc here that would clarify the expectations contributors might have when opening PRs targeting that label?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Backlog Bug PRs that fix a backlog bug
Projects
Status: Not started
Development

Successfully merging this pull request may close these issues.

No type inferrence of callback arguments inside a tuple union type
4 participants