Repository navigation
Parameter signatures of functions in object literals are not always inferred correctly. #4241
Description
Activity
- changed the title
[-]Types not inferred correctly[/-][+]Parameter signatures of functions in object literals are not inferred correctly.[/+]on Aug 8, 2015 - changed the title
[-]Parameter signatures of functions in object literals are not inferred correctly.[/-][+]Parameter signatures of functions in object literals are not always inferred correctly.[/+]on Aug 8, 2015 DanielRosenwasser commented
on Aug 9, 2015 MemberMore actionsThis is actual per spec. From section 4.10 Function Expressions:
When a function expression with no type parameters and no parameter type annotations is contextually typed (section 4.23) by a type T and a contextual signature S can be extracted from T, the function expression is processed as if it had explicitly specified parameter type annotations as they exist in S.
The rule is effectively that a function expression isn't contextually typed if it has any type annotations on its parameters.
Just because it says that in the spec doesn't mean that it makes any sense. The compiler should infer missing types at the same positions when a type is missing on the function expression. Just doesn't make any sense really. At the very least, it should see the given parameter type "string" as compatible with "any" and continue to infer the other types (especially if the given parameter type is optional!). It's really annoying to have a list of functions on an object all of the same supporting signatures, and for some, having to explicitly specify the missing parameter types that don't infer.
Bottom line, since
bis optional (based on the interface signature), and has a type that can be assigned to the optional parameters, the missing type(s) should infer (in fact, with "no implicit any", I'm sure it would err out in the case above).wow...
guys, you keep making people really pissed off with all your specs and the way you do you work, you need to double your effort and stop letting good developers down, jeez..
- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptIn DiscussionNot yet reached consensusNot yet reached consensus
on Aug 10, 2015 JsonFreeman commented
on Aug 14, 2015 ContributorMore actionsI believe the rationale here is that you either want the function's entire parameter list explicitly typed, or the entire parameter list inferred. It has been spec'd this way because the thinking was, if the user put a type annotation on any parameter, it means the user wants the type system to back off and let them do the typing.
In this case, it looks like the intent of the contextual signature was to be exact/specific about the type of one parameter, and vague about others (in this case the rest). Then when contextually typing a function expression, the specific type should be automatically assigned to the corresponding parameter in the function expression, but the "vague type" should be overridden by the explicit type annotation in the function expression.
The compiler would need some way to know whether the explicit type annotations in the function expression correspond to positions of "vague types" in the contextual signature. If so, the remaining parameters should have their types assigned by contextual typing. If not, then it is a signal that the user wants these remaining parameters to be of type
any. Is there any meaningful heuristic for whether a parameter in the contextual signature is "vague"?Sounds about right. I expected that if a parameter type of a function expression in context is missing, and the other parameters are of a same or super type (or 'any') to the expected signature in the context, that it is quite obvious the developer wants the missing parameter types inferred.
DanielRosenwasser commented
on Aug 14, 2015 MemberMore actionsAleksey-Bykov James (@rjamesnw) my apologies. When I said it's in the spec, I didn't mean to shoot you down - that's why I didn't close the issue as "by design". I mostly wanted to just give context that this isn't a compiler bug, but rather, a potential improvement for the language.
I didn't realize this was the case either, and indeed, I expected the un-annotated parameters to be contextually typed as well.
Reacted by Herrington Darkholme and SanthoshJsonFreeman commented
on Aug 14, 2015 ContributorMore actionsIt would be a breaking change though, if we suddenly contextually typed all missing parameters. It's just a question of how many partially annotated contextually type-able parameter lists there are out there.
Only for those not using "no implicit any". ;) And even then, it would be of type "any", so I'm guessing it wouldn't break much. In fact, it'll probably help people find bugs and push them to add proper types for the rare cases. I think there are more benefits than not.
Daniel Rosenwasser (@DanielRosenwasser) I was kidding when I said that (wasn't too funny I know), but James (@rjamesnw) seemed seriously upset
Annoyed, but not upset, lol. ;)
DanielRosenwasser commented
on Aug 15, 2015 MemberMore actionsIt'd be a breaking change, but the workaround would be casting the function to
any, so I don't think it would be terrible.Aleksey-Bykov tone sometimes doesn't transfer online all that well, but I can sorta see it now - James (@rjamesnw) awesome, as long as you're not upset! 😄
8 remaining items
- added this to the This milestone has been deleted milestone
on Oct 5, 2015 - addedBreaking ChangeWould introduce errors in existing codeWould introduce errors in existing code
on Oct 5, 2015 RyanCavanaugh commented
on Oct 5, 2015 MemberMore actionsApproved for PRs. This should be an easyish fix for anyone who's interested in trying out a compiler change.
sandersn commented
on Jul 13, 2016 MemberMore actionsActually, I don't think it will be that easy because contextual typing interacts with type parameter inference. And our type inference is not easy. Here's an example based on discussion with Mohamed Hegazy (@mhegazy) and Daniel Rosenwasser (@DanielRosenwasser) yesterday:
declare function makeSorter<T>(gt: (t1: T, t2: T) => boolean): (array: T[]) => T[]; makeSorter((n: number, m) => n > m); // T=any, n: number, m: any makeSorter((n, m: number) => n > m); // T=any, n: number, m: any
What you actually want is
T=number, n: number, m: numberfor both calls. Unfortunately, type inference currently skips contextually typed functions entirely and tries them in a second round, hoping that information gathered from other parameters will finish up inference first. This change would require inference to extract information from partially annotated functions in the first round.Here's a way that might work: in the first round of inference,
- Use only annotated parameters of function expressions for inference
- Come up with some kind of Skip type for unannotated parameters (any, maybe?) so that inference can skip single parameters instead of the whole function.
Now in the second round of inference,
- Handle partially annotated functions as well as completely unannotated ones.
- Only infer from unannotated parameters (the ones we skipped in the first round).
I haven't tried this so of course it might not work.
- addedEffort: ModerateRequires experience with the TypeScript codebase, but feasible. Harder than "Effort: Casual".Requires experience with the TypeScript codebase, but feasible. Harder than "Effort: Casual".and removedGood First IssueWell scoped, documented and has the green lightWell scoped, documented and has the green light
on Jul 13, 2016 JsonFreeman commented
on Jul 13, 2016 ContributorMore actionsOne other way you could do it is:
- In the first pass, you skip a function if any of its parameters would be contextually typed. In the example above,
gtwould be skipped. - In the second pass, when you visit a function, process the annotated parameters first (to add inference candidates), and then fix the type parameters for any bare parameters they align with (which I believe happens automatically when contextually typing those parameters).
- In the first pass, you skip a function if any of its parameters would be contextually typed. In the example above,
Dang, I just got bitten by this. It took a long session of reducing ~200 lines of fairly complex generic types to get down to the following simplified repro, where the annotation issue is finally obvious. I couldn't make any sense of the error message in the original code. But once I worked out it was this issue, the first line of the error message was a giveaway.
declare function foo<T>(cb: Callback<T>): void; interface Callback<T> { (val: T, captures: {[name: string]: string}, next: Function): void; } foo<string>((val, {n}, next) => {}); // OK, val is string, n is string, next is Function foo((val: string, {n}, next) => {}); // ERROR // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // src/index.ts(14,5): error TS2345: Argument of type '(val: string, {n}: { n: any; }, next: any) => void' is not assignable to parameter of type 'Callback<string>'. // Types of parameters '__1' and 'captures' are incompatible. // Type '{ [name: string]: string; }' is not assignable to type '{ n: any; }'. // Property 'n' is missing in type '{ [name: string]: string; }'.
HerringtonDarkholme commented
on Oct 11, 2016 ContributorMore actionsI have a proposal for this. For background and example please see #11475 . TypeScript needs this change even more than before because of
thistype and library paradigm shift (flux like library).The key difference from Nathan Shively-Sanders (@sandersn) 's proposal is how type parameter is inferred. I choose to avoid two rounds inference: type parameter is fixed if preceding parameter is unannotated. And following parameter annotation has no effect on fixed type parameter.Changed mind. I think double passes are better ergonomics.
Proposal
Partially annotated function is contextually inferred. We need first define what a partially annotated function.
A partially annotated function is a function expression that has no parameter or has at least one parameter missing type annotation.
No parameter is for compatibility like below.
interface A { contextualTypeThis(this: {a: number}): void } var a: A = { contextualTypeThis: function() { this.a } }To enable contextual typing on partially annotated function requires several modification to spec.
In #4.10
When a function expression with no type parameters and no parameter type annotations is contextually typed.
is changed to
When a partially annotated function expression with no type parameters is contextually typed.
And in #4.15.2, Type parameter inference is not necessarily fixed when function expression argument is inferred.
When a function expression is inferentially typed (section 4.10), all annotated parameters are used to produce candidates for type parameters referenced in types assigned to corresponding parameter. After that, for unannotated parameters, if a type is assigned to one parameter in that expression references type parameters for which inferences are being made, the corresponding inferred type arguments to become fixed and no further candidate inferences are made for them.
This is basically two round inference.
Compatiblity
This will introduce breaking changes. But I have no idea how will it impact real world code
A parameter used to be inferred as
anywill become inferred to some type. This is a breaking change.The above example is also a breaking change, the return type is currently inferred as
any.Overloading resolution probably is not influenced because contextual typing will succeed for a unannotated argument.
Implementaion
I have a experimental branch for partial annotation, and some new tests (still needs a lot more). It seems modification to existing code is not massive.
- added a commit that references this issue
on Oct 11, 2016 - addedFixedA PR has been merged for this issueA PR has been merged for this issue
on Oct 18, 2016 - locked and limited conversation to collaborators
on Jun 19, 2018
Hi, I ran into an issue where the types don't seem to be inferred correctly. Consider this:
I expect "a" for "handler2" to be "string", but instead it is "any". Shouldn't "a" be interred from the signature, since the "args" are of type "any", which should match any type?
Thanks.