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

Combine multiple overloads into a single contextual signature #42620

Conversation

weswigham
Copy link
Member

With this PR, we combine multiple overloads into a single contextual signature when noImplcitAny is set (for backwards compatibility, since otherwise these positions had type any). The rules for signature combining are similar to union signature combining, but swapping union/intersection rules where applicable; Either no generics, or identical generic lists, Parameters (and this parameters) union together, return types (and predicates) intersect (return types/predicates are likely visible via return type inference, so do need sensible rules).

With the caveat that this only changes our behavior when noImplicitAny is set, this:
Fixes #42559
Fixes #42504
Doesn't change #38625 - since the signatures in question have a mix of type parameters and no type parameters.
Doesn't change #35641 - there's no contextual typing involved; generic signature resolution has a similar drawback where it doesn't handle overloads which this PR does not touch.
Fixes this comment though not the containing issue (which is moreso a dupe/precursor of #35641)

@typescript-bot typescript-bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Feb 2, 2021
@weswigham
Copy link
Member Author

@typescript-bot perf test this
@typescript-bot user test this
@typescript-bot run dt
@typescript-bot test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Feb 2, 2021

Heya @weswigham, I've started to run the parallelized community code test suite on this PR at 429b88f. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Feb 2, 2021

Heya @weswigham, I've started to run the parallelized Definitely Typed test suite on this PR at 429b88f. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Feb 2, 2021

Heya @weswigham, I've started to run the extended test suite on this PR at 429b88f. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Feb 2, 2021

Heya @weswigham, I've started to run the perf test suite on this PR at 429b88f. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

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

Here they are:

Comparison Report - master..42620

Metric master 42620 Delta Best Worst
Angular - node (v10.16.3, x64)
Memory used 346,406k (± 0.03%) 346,444k (± 0.02%) +38k (+ 0.01%) 346,262k 346,645k
Parse Time 1.92s (± 0.55%) 1.91s (± 0.55%) -0.01s (- 0.52%) 1.89s 1.93s
Bind Time 0.82s (± 0.73%) 0.82s (± 0.60%) -0.00s (- 0.12%) 0.81s 0.83s
Check Time 5.01s (± 0.59%) 4.97s (± 0.47%) -0.04s (- 0.74%) 4.93s 5.02s
Emit Time 5.27s (± 0.82%) 5.24s (± 0.66%) -0.03s (- 0.57%) 5.18s 5.32s
Total Time 13.03s (± 0.43%) 12.96s (± 0.40%) -0.08s (- 0.58%) 12.86s 13.07s
Compiler-Unions - node (v10.16.3, x64)
Memory used 214,972k (± 0.02%) 214,953k (± 0.04%) -20k (- 0.01%) 214,734k 215,092k
Parse Time 0.78s (± 0.63%) 0.77s (± 0.91%) -0.01s (- 0.64%) 0.76s 0.79s
Bind Time 0.50s (± 0.75%) 0.49s (± 1.01%) -0.00s (- 0.00%) 0.48s 0.50s
Check Time 10.72s (± 0.84%) 10.70s (± 0.40%) -0.02s (- 0.18%) 10.62s 10.79s
Emit Time 2.37s (± 1.51%) 2.33s (± 1.24%) -0.04s (- 1.77%) 2.28s 2.40s
Total Time 14.35s (± 0.73%) 14.29s (± 0.33%) -0.06s (- 0.42%) 14.18s 14.39s
Monaco - node (v10.16.3, x64)
Memory used 355,237k (± 0.04%) 355,331k (± 0.03%) +94k (+ 0.03%) 355,149k 355,586k
Parse Time 1.55s (± 0.71%) 1.55s (± 0.40%) +0.00s (+ 0.19%) 1.54s 1.56s
Bind Time 0.73s (± 0.65%) 0.73s (± 0.65%) -0.00s (- 0.00%) 0.72s 0.74s
Check Time 5.12s (± 0.58%) 5.14s (± 0.47%) +0.02s (+ 0.41%) 5.07s 5.19s
Emit Time 2.79s (± 0.67%) 2.78s (± 0.54%) -0.01s (- 0.36%) 2.75s 2.83s
Total Time 10.18s (± 0.53%) 10.19s (± 0.30%) +0.01s (+ 0.15%) 10.10s 10.25s
TFS - node (v10.16.3, x64)
Memory used 308,160k (± 0.02%) 308,198k (± 0.02%) +38k (+ 0.01%) 308,102k 308,311k
Parse Time 1.20s (± 0.46%) 1.20s (± 0.69%) +0.00s (+ 0.17%) 1.19s 1.22s
Bind Time 0.68s (± 0.59%) 0.68s (± 0.95%) +0.00s (+ 0.44%) 0.67s 0.70s
Check Time 4.59s (± 0.51%) 4.60s (± 0.57%) +0.02s (+ 0.33%) 4.54s 4.67s
Emit Time 2.91s (± 0.46%) 2.92s (± 0.91%) +0.01s (+ 0.31%) 2.85s 3.00s
Total Time 9.38s (± 0.33%) 9.41s (± 0.56%) +0.03s (+ 0.32%) 9.26s 9.55s
material-ui - node (v10.16.3, x64)
Memory used 496,311k (± 0.01%) 496,227k (± 0.01%) -84k (- 0.02%) 496,125k 496,390k
Parse Time 1.98s (± 0.82%) 1.97s (± 0.44%) -0.01s (- 0.55%) 1.95s 1.99s
Bind Time 0.65s (± 0.80%) 0.65s (± 0.68%) +0.00s (+ 0.15%) 0.64s 0.66s
Check Time 13.98s (± 0.46%) 13.95s (± 0.34%) -0.04s (- 0.26%) 13.82s 14.04s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 16.62s (± 0.45%) 16.57s (± 0.32%) -0.05s (- 0.29%) 16.42s 16.67s
Angular - node (v12.1.0, x64)
Memory used 323,732k (± 0.11%) 324,112k (± 0.02%) +380k (+ 0.12%) 323,980k 324,318k
Parse Time 1.90s (± 0.59%) 1.90s (± 0.39%) +0.00s (+ 0.21%) 1.88s 1.92s
Bind Time 0.80s (± 0.72%) 0.80s (± 1.02%) +0.00s (+ 0.00%) 0.78s 0.82s
Check Time 4.91s (± 1.12%) 4.89s (± 0.43%) -0.02s (- 0.35%) 4.84s 4.94s
Emit Time 5.45s (± 2.07%) 5.39s (± 0.62%) -0.06s (- 1.05%) 5.33s 5.48s
Total Time 13.05s (± 1.06%) 12.98s (± 0.31%) -0.07s (- 0.55%) 12.89s 13.06s
Compiler-Unions - node (v12.1.0, x64)
Memory used 200,361k (± 0.06%) 200,345k (± 0.07%) -16k (- 0.01%) 200,054k 200,592k
Parse Time 0.76s (± 0.85%) 0.77s (± 0.44%) +0.00s (+ 0.66%) 0.76s 0.77s
Bind Time 0.50s (± 0.89%) 0.50s (± 0.80%) +0.00s (+ 0.20%) 0.49s 0.51s
Check Time 9.78s (± 0.77%) 9.82s (± 0.62%) +0.04s (+ 0.37%) 9.73s 9.95s
Emit Time 2.32s (± 1.48%) 2.36s (± 1.90%) +0.04s (+ 1.55%) 2.29s 2.47s
Total Time 13.36s (± 0.69%) 13.44s (± 0.46%) +0.08s (+ 0.60%) 13.30s 13.56s
Monaco - node (v12.1.0, x64)
Memory used 337,549k (± 0.02%) 337,509k (± 0.02%) -40k (- 0.01%) 337,334k 337,699k
Parse Time 1.53s (± 0.71%) 1.53s (± 0.53%) +0.01s (+ 0.33%) 1.52s 1.56s
Bind Time 0.70s (± 1.06%) 0.70s (± 0.67%) -0.00s (- 0.43%) 0.69s 0.71s
Check Time 4.91s (± 0.49%) 4.94s (± 0.42%) +0.03s (+ 0.57%) 4.90s 4.99s
Emit Time 2.84s (± 0.65%) 2.84s (± 0.66%) +0.00s (+ 0.11%) 2.81s 2.89s
Total Time 9.97s (± 0.39%) 10.01s (± 0.43%) +0.04s (+ 0.40%) 9.95s 10.14s
TFS - node (v12.1.0, x64)
Memory used 292,428k (± 0.03%) 292,385k (± 0.03%) -43k (- 0.01%) 292,194k 292,546k
Parse Time 1.22s (± 0.41%) 1.22s (± 0.57%) +0.00s (+ 0.33%) 1.20s 1.23s
Bind Time 0.65s (± 0.85%) 0.66s (± 0.76%) +0.00s (+ 0.46%) 0.64s 0.66s
Check Time 4.50s (± 0.44%) 4.50s (± 0.48%) +0.00s (+ 0.09%) 4.46s 4.55s
Emit Time 2.92s (± 0.55%) 2.94s (± 0.96%) +0.01s (+ 0.48%) 2.86s 3.00s
Total Time 9.29s (± 0.32%) 9.31s (± 0.46%) +0.02s (+ 0.27%) 9.19s 9.41s
material-ui - node (v12.1.0, x64)
Memory used 473,379k (± 0.06%) 473,482k (± 0.01%) +103k (+ 0.02%) 473,400k 473,633k
Parse Time 1.99s (± 0.62%) 1.99s (± 0.42%) -0.00s (- 0.05%) 1.97s 2.01s
Bind Time 0.64s (± 0.62%) 0.64s (± 0.46%) +0.00s (+ 0.16%) 0.64s 0.65s
Check Time 12.54s (± 0.67%) 12.55s (± 0.51%) +0.01s (+ 0.06%) 12.42s 12.77s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 15.18s (± 0.56%) 15.19s (± 0.45%) +0.01s (+ 0.06%) 15.04s 15.41s
Angular - node (v14.15.1, x64)
Memory used 322,620k (± 0.01%) 322,669k (± 0.01%) +49k (+ 0.02%) 322,564k 322,703k
Parse Time 1.91s (± 0.43%) 1.91s (± 0.52%) -0.00s (- 0.05%) 1.89s 1.94s
Bind Time 0.85s (± 0.82%) 0.85s (± 1.07%) 0.00s ( 0.00%) 0.84s 0.88s
Check Time 4.89s (± 0.34%) 4.90s (± 0.31%) +0.01s (+ 0.20%) 4.87s 4.93s
Emit Time 5.47s (± 0.20%) 5.48s (± 0.73%) +0.01s (+ 0.16%) 5.41s 5.61s
Total Time 13.12s (± 0.16%) 13.14s (± 0.40%) +0.02s (+ 0.14%) 13.06s 13.29s
Compiler-Unions - node (v14.15.1, x64)
Memory used 201,184k (± 0.55%) 200,816k (± 0.50%) -367k (- 0.18%) 199,630k 203,457k
Parse Time 0.80s (± 0.56%) 0.79s (± 0.46%) -0.00s (- 0.50%) 0.79s 0.80s
Bind Time 0.53s (± 0.42%) 0.53s (± 0.00%) +0.00s (+ 0.19%) 0.53s 0.53s
Check Time 9.85s (± 1.24%) 9.81s (± 0.74%) -0.05s (- 0.47%) 9.63s 9.93s
Emit Time 2.34s (± 0.90%) 2.33s (± 0.97%) -0.01s (- 0.38%) 2.31s 2.40s
Total Time 13.52s (± 0.93%) 13.47s (± 0.68%) -0.05s (- 0.41%) 13.26s 13.65s
Monaco - node (v14.15.1, x64)
Memory used 336,797k (± 0.01%) 336,798k (± 0.01%) +1k (+ 0.00%) 336,748k 336,854k
Parse Time 1.57s (± 0.53%) 1.56s (± 0.60%) -0.01s (- 0.51%) 1.54s 1.58s
Bind Time 0.73s (± 0.67%) 0.74s (± 0.51%) +0.00s (+ 0.14%) 0.73s 0.74s
Check Time 4.86s (± 0.42%) 4.85s (± 0.28%) -0.00s (- 0.02%) 4.83s 4.89s
Emit Time 2.90s (± 0.63%) 2.90s (± 0.58%) +0.00s (+ 0.07%) 2.86s 2.95s
Total Time 10.05s (± 0.25%) 10.05s (± 0.26%) -0.01s (- 0.07%) 9.98s 10.11s
TFS - node (v14.15.1, x64)
Memory used 291,551k (± 0.01%) 291,593k (± 0.00%) +42k (+ 0.01%) 291,566k 291,615k
Parse Time 1.26s (± 1.08%) 1.24s (± 1.04%) -0.01s (- 1.03%) 1.22s 1.28s
Bind Time 0.69s (± 1.08%) 0.69s (± 0.87%) -0.00s (- 0.58%) 0.67s 0.70s
Check Time 4.48s (± 0.44%) 4.50s (± 0.52%) +0.02s (+ 0.54%) 4.45s 4.56s
Emit Time 3.04s (± 0.59%) 3.04s (± 0.67%) +0.01s (+ 0.23%) 3.00s 3.09s
Total Time 9.46s (± 0.28%) 9.48s (± 0.37%) +0.02s (+ 0.18%) 9.39s 9.55s
material-ui - node (v14.15.1, x64)
Memory used 472,112k (± 0.06%) 472,154k (± 0.06%) +42k (+ 0.01%) 471,007k 472,344k
Parse Time 2.05s (± 0.56%) 2.04s (± 0.63%) -0.01s (- 0.63%) 2.01s 2.06s
Bind Time 0.69s (± 0.72%) 0.70s (± 0.49%) +0.00s (+ 0.29%) 0.69s 0.70s
Check Time 12.62s (± 0.66%) 12.56s (± 0.42%) -0.06s (- 0.50%) 12.43s 12.69s
Emit Time 0.00s (± 0.00%) 0.00s (± 0.00%) 0.00s ( NaN%) 0.00s 0.00s
Total Time 15.37s (± 0.53%) 15.29s (± 0.34%) -0.07s (- 0.48%) 15.18s 15.43s
System
Machine Namets-ci-ubuntu
Platformlinux 4.4.0-198-generic
Architecturex64
Available Memory16 GB
Available Memory8 GB
CPUs4 × Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
Hosts
  • node (v10.16.3, x64)
  • node (v12.1.0, x64)
  • node (v14.15.1, x64)
Scenarios
  • Angular - node (v10.16.3, x64)
  • Angular - node (v12.1.0, x64)
  • Angular - node (v14.15.1, x64)
  • Compiler-Unions - node (v10.16.3, x64)
  • Compiler-Unions - node (v12.1.0, x64)
  • Compiler-Unions - node (v14.15.1, x64)
  • Monaco - node (v10.16.3, x64)
  • Monaco - node (v12.1.0, x64)
  • Monaco - node (v14.15.1, x64)
  • TFS - node (v10.16.3, x64)
  • TFS - node (v12.1.0, x64)
  • TFS - node (v14.15.1, x64)
  • material-ui - node (v10.16.3, x64)
  • material-ui - node (v12.1.0, x64)
  • material-ui - node (v14.15.1, x64)
Benchmark Name Iterations
Current 42620 10
Baseline master 10

@typescript-bot
Copy link
Collaborator

The user suite test run you requested has finished and failed. I've opened a PR with the baseline diff from master.

@weswigham
Copy link
Member Author

weswigham commented Feb 4, 2021

Perf is good, rwc looks good (1 change removing an implicit any exactly where this should), user tests look clean, and DT brought up one change which is somewhat interesting - a function contextually typed by a type like:

((match: { x: string; }) => void) | { (form: "NFC" | "NFD" | "NFKC" | "NFKD"): string; (form?: string | undefined): string; } | undefined

previously, that 2-overload union member would produce no contextual information (since it had two overloads), making the 1-overload union the only union member with a visible contextual signature. Now, both union members produce a signature, they aren't identical, and the contextual signature is dropped entirely. I can preserve our current behavior here (dropping the two-overload's contextual information) without changing the behavior outside of union contextual types, but I'm unsure if we should? Certainly, we feel justified giving no contextual signature for

((match: { x: string; }) => void) | { (form: "NFC" | "NFD" | "NFKC" | "NFKD"): string; } | undefined

so why should the addition of an overload change that?

@weswigham
Copy link
Member Author

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Feb 8, 2021

Heya @weswigham, I've started to run the tarball bundle task on this PR at 33a5727. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Feb 9, 2021

Hey @weswigham, 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/95281/artifacts?artifactName=tgz&fileId=83BA68DE118EC9D31EF448A2AC81BDAA96BC0051E358379D9F308B08C0EF347902&fileName=/typescript-4.2.0-insiders.20210209.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@4.2.0-pr-42620-10".;

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

Looks pretty good. I have a couple of questions and a lot of minor style suggestions.

@@ -1,8 +1,8 @@
=== tests/cases/compiler/redefineArray.ts ===
Array = function (n:number, s:string) {return n;};
>Array = function (n:number, s:string) {return n;} : (n: number, s: string) => number
>Array = function (n:number, s:string) {return n;} : <T>(n: number, s: string) => number
Copy link
Member

Choose a reason for hiding this comment

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

The addition of the type parameter seems a little odd. Why does a contextual signature give this function a type parameter?

Copy link
Member Author

Choose a reason for hiding this comment

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

Array's call signature has type parameters (and multiple overloads, hence why we only now pick it up), so our contextual signature logic says to persist those type parameters onto the inferred signature (so they can be inferred and used at the parameter types). That type parameter just happens to end up unused, since the parameters are all annotated with types that override the inferred ones. Specifically, it has the signatures:

    (arrayLength?: number): any[];
    <T>(arrayLength: number): T[];
    <T>(...items: T[]): T[];

so it used to be that you'd get no contextual signature whatsoever, whereas now the contextual signature is something like <T>(arrayLengthOrItem?: number | T, ...items: T[]): T[]. Technically any time a contextually typed signature has all the parameter types specified, we could probably omit these type parameters. Probably. You can see similar behavior in the types baselines today with only one overload and something like:

// @strictFunctionTypes: false
interface MyCallable {
    <T>(a: T | number): T[];
}

const x: MyCallable = function (arg: number) { return null as any };

(strictFunctionTypes has to be off for the assignment to succeed, in both cases). I don't know why, but we also only do this for function expressions and not arrow functions... that's probably indicative of some bug somewhere; but it's a preexisting one.

Copy link
Member

Choose a reason for hiding this comment

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

OK, I figured it was pre-existing.

const obj: {field: Rule} = {
field: {
validate: (_t, _p, _s) => false,
normalize: match => match.x,
Copy link
Member

Choose a reason for hiding this comment

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

why doesn't this one get a contextual signature too?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a test capturing this change I describe from DT - that comment contains the explanation, and is the bulk of what I was trying to discuss at the design meeting.

@@ -12045,13 +12049,17 @@ namespace ts {
findIndex(signature.parameters, p => p.escapedName === parameterName.escapedText), type);
}

function getUnionOrIntersectionType(types: Type[], kind: TypeFlags | undefined, unionReduction?: UnionReduction) {
return kind !== TypeFlags.Intersection ? getUnionType(types, unionReduction) : getIntersectionType(types);
Copy link
Member

Choose a reason for hiding this comment

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

Is this still correct? It's easier to read.

Suggested change
return kind !== TypeFlags.Intersection ? getUnionType(types, unionReduction) : getIntersectionType(types);
return kind === TypeFlags.Union ? getUnionType(types, unionReduction) : getIntersectionType(types);

Copy link
Member Author

@weswigham weswigham Feb 9, 2021

Choose a reason for hiding this comment

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

I intentionally made all the comparisons kind !== TypeFlags.Intersection so that the kind being undefined is synonymous with Union (to preserve the existing structure where there is no kind). Now, I don't intentionally leave kind undefined anywhere, but just in case, I've written the comparisons to be resilient to it (since it is an "optional" signature member, and maybe some plugin author manufactures signatures and send them into the checker, who knows).

src/compiler/checker.ts Outdated Show resolved Hide resolved
@@ -24830,7 +24843,7 @@ namespace ts {
}
results.push(propType);
}
return getIntersectionType(results);
return getIntersectionType(results); // Same result for both union and intersection signatures
Copy link
Member

Choose a reason for hiding this comment

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

why does this need to be commented? It didn't surprise me since no other code changed around here, so I think I'm missing the actual surprising thing.

Copy link
Member Author

Choose a reason for hiding this comment

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

The actual surprising thing is that nothing changed here. This code path handled union composite signatures... and now we're handling intersection composite signatures in the same way (rather than inverting anything), which is odd. The reason for that is explained in this comment (namely that for compat reasons we intentionally do "the wrong thing" for union signatures here, which happens to be "the right thing" for intersection signatures)

}
// A signature `this` type might be a read or a write position... It's very possible that it should be invariant
// and we should refuse to merge signatures if there are `this` types and they do not match. However, so as to be
// pessimistic when contextual typing, for now, we'll union the `this` types.
Copy link
Member

Choose a reason for hiding this comment

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

what would cause us to change this? (not a big fan of 'for now'-style comments because they don't have enough context with them)

Copy link
Member

Choose a reason for hiding this comment

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

also: this is basically a parameter, so it should union just like the other parameters.

Copy link
Member Author

Choose a reason for hiding this comment

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

so it should union just like the other parameters.

And so we are, the comment is just an inversion of the comment that already exists in combineUnionThisParam which does the opposite.

return params;
}

function combineSignaturesOfIntersectionMembers(left: Signature, right: Signature): Signature {
Copy link
Member

Choose a reason for hiding this comment

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

this should probably come before combineIntersectionParameters and combineIntersectionThisParam

Copy link
Member Author

Choose a reason for hiding this comment

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

This is in the same order that the very similar combineUnionParameters and combineUnionThisParam are~

minArgCount,
(left.flags | right.flags) & SignatureFlags.PropagatingFlags
);
result.compositeKind = TypeFlags.Intersection;
Copy link
Member

Choose a reason for hiding this comment

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

stupid efficiency question: Is it more efficient to have createSignature set these? (I'm guessing not, since probably composite signatures (1) are rare (2) have their own internal class.)

Copy link
Member Author

Choose a reason for hiding this comment

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

createSignature sets them to undefined already, along with every other optional field on signatures~

Comment on lines 24958 to 24959
const longest = leftCount >= rightCount ? left : right;
const shorter = longest === left ? right : left;
Copy link
Member

Choose a reason for hiding this comment

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

could they be named longest/shortest or longer/shorter?

Copy link
Member Author

Choose a reason for hiding this comment

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

This follows the same terminology we already use in combineUnionParameters, which combinedIntersectionParameters really closely mirrors.

Comment on lines 24979 to 24982
const paramName = leftName === rightName ? leftName :
!leftName ? rightName :
!rightName ? leftName :
undefined;
Copy link
Member

Choose a reason for hiding this comment

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

uhhhhhhhhh, why not just leftName || rightName? I'm not even sure it's observable, but even if it is, some name is better than arg0.

Copy link
Member Author

@weswigham weswigham Feb 9, 2021

Choose a reason for hiding this comment

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

Because that's what we merged for combineUnionParameters in #32056 :V

Also, "some name is better than arg0" isn't really true. Names carry semantic meaning, so let's say you have (index: number) => any and (object: Whatever): any - calling the result index where's it's type is number | Whatever has the potential to be misleading. Particularly problematic is when the types are the same, but semantically different, like
(length: number): any[] and (firstElement: number): any[] - yeah, number is the type of both, but there're not semantically the same! So calling both "length" or "firstElement" would be dead wrong. Now, I had a super old version of this (like, years old. insert "deja vu" theme here) that concatenated the names in cases like this, to lengthOrFirstElement, but that was shot down as too easily producing names that are unwieldy as unions grow. And thus, the preference for arg0 was born. It carries no information, thus you can draw no (potentially inaccurate) conclusion as to the usage of the parameter.

Copy link
Member

Choose a reason for hiding this comment

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

If @dragomirtitian cares enough to fix it, that's good enough for me. =)

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

Looks good to me...with the caveat that the minor style issues that stem from combineUnionParameters precedent should be fixed there and here in a followup PR =P

@weswigham
Copy link
Member Author

@DanielRosenwasser I assume you want this to hit 4.3 and not 4.2 at this point - will you LMK when I should merge?

@jcalz
Copy link
Contributor

jcalz commented Feb 13, 2022

SO Question asks about this strange behavior:

interface Ovld {
    (): void;
    (x: string): void;
}
const oNo: Ovld = x => { }
// -> ~~~
// error! Type '(x: string) => void ' is not assignable to type 'Ovld'.

const ok: Ovld = (x?: string) => { } // okay

Playground link

Looks like the contextual signature isn't what I'd expect in the face of differing parameter list lengths. Wondering if this behavior is intended or unintended as per this PR, and if there should be a new issue filed about it.

@forresthopkinsa
Copy link

For the sake of search, this also fixes an unmentioned bug (though somewhat similar to the comment from 35641):

const obj = {
    a: ['x'],
    b: ['y'],
} as const;

type Obj = typeof obj;
type Key = keyof Obj;
type Val = Obj[Key];
type Item = Val[number];

const keys = Object.keys(obj) as Key[];

keys.forEach(key => {
    obj[key].forEach(item => { // Parameter 'item' implicitly has an 'any' type. (7006)
        console.log(item);
    });
    
    for (const item of obj[key]) { // no error, type inferred correctly
        console.log(item);
    }
});

Playground

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Possible breaking change: assigning array to 1-tuple callbacks should select the right overload
5 participants