Fix lambda contextual typing with keyword-only callables - #11609
Fix lambda contextual typing with keyword-only callables#11609Kirill (vetrovk) wants to merge 3 commits into
Conversation
|
🔒 Automated review in progress — Stella Huang (@StellaHuang95) is auto-reviewing this PR. |
| positional_callable_union: Callable[[PositionalCallable], PositionalCallable] | PositionalCallable = lambda x: x | ||
|
|
||
| # This should generate an error. | ||
| keyword_only_callback: KeywordOnlyCallback = lambda x: x |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
Add a positive regression case for the enabled branch: a lambda with *args followed by a keyword-only parameter contextually typed against a keyword-only callable. The new guard's rejection behavior is well covered, but this ensures valid keyword-only lambda parameters still receive their expected type.
[verified]
| if ( | ||
| expectedParam.param.category === param.d.category && | ||
| !param.d.name === !expectedParam.param.name | ||
| !param.d.name === !expectedParam.param.name && |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
This guard also prevents contextual typing for a compatible single callable such as Callable[*, value: int] assigned from lambda value: value: that lambda accepts the required value= keyword, but its parameter now falls back to Unknown. Please preserve contextual typing when the lambda parameter name matches the keyword-only expected parameter, or add coverage showing why this case must be rejected.
[verified]
|
Please mirror the keyword-only lambda-parameter compatibility guard in |
| isPrivateName(param.d.name.d.value)); | ||
| const isCompatibleKeywordParam = | ||
| expectedParam.kind !== ParamKind.Keyword || | ||
| sawLambdaArgsParam || |
There was a problem hiding this comment.
Info · Optional note
isPositionOnlyParam reads paramsArePositionOnly, which is mutated later in this loop. It is correct today, but that coupling makes the matching behavior sensitive to loop ordering. Consider deriving this status up front from node.d.params, alongside positionOnlySeparatorIndex, so future refactoring cannot silently change contextual-typing behavior.
[verified]
Stella Huang (StellaHuang95)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
Rich Chiodo (rchiodo)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
Rich Chiodo (rchiodo)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
|
🔒 Automated review in progress — Rich Chiodo (@rchiodo) is auto-reviewing this PR. |
| const expectedParam = expectedParamDetails.params[index]; | ||
| const isPositionOnlyParam = | ||
| (positionOnlySeparatorIndex >= 0 && index < positionOnlySeparatorIndex) || | ||
| (positionOnlySeparatorIndex < 0 && |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
A bare * appears to consume an index even though contextual parameter details omit separators, potentially leaving lambda *, value: ... without the contextual type for value. Add that protocol scenario with assert_type(value, int) and, if confirmed, track lambda and contextual indexes separately.
| paramsArePositionOnly && | ||
| !!param.d.name && | ||
| isPrivateName(param.d.name.d.value)); | ||
| const isCompatibleKeywordParam = |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
Once sawLambdaArgsParam is true, any keyword-only contextual parameter is accepted regardless of its name, so lambda *args, other: ... may inherit the type of contextual value. Add a callable-union regression using assert_type(other, ...); require matching names after *args if the candidate can otherwise influence inference.
| FunctionType.addParam(functionType, functionParam); | ||
|
|
||
| if (param.d.category === ParamCategory.ArgsList) { | ||
| sawLambdaArgsParam = true; |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
Pylance's parallel evaluator still contains the previous matching condition, so vendoring this change without mirroring it could produce sync/async inference differences. Ensure the downstream ingestion updates that evaluator and runs this regression in both modes.
Rich Chiodo (rchiodo)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
Heejae Chang (heejaechang)
left a comment
There was a problem hiding this comment.
Approved via Review Center.
| node.d.params.forEach((param, index) => { | ||
| let paramType: Type | undefined; | ||
|
|
||
| if (expectedParamDetails && !sawParamMismatch) { |
There was a problem hiding this comment.
Issue · Please address or respond
Can the unnamed bare * be normalized separately from the contextual parameter index? If getParamListDetails omits that separator, lambda *, value: ... compares * with the expected value parameter, marks a mismatch, and never contextually types value. Please skip or align the separator and add this regression case.
| sawLambdaArgsParam || | ||
| (!isPositionOnlyParam && param.d.name?.d.value === expectedParam.param.name); | ||
|
|
||
| // If the parameter category matches and both of the parameters are |
There was a problem hiding this comment.
Issue · Please address or respond
Can this continue to require matching keyword names after *args? sawLambdaArgsParam currently accepts every following keyword-only expected parameter regardless of name, but lambda *args, value: ... does not accept other=.... This can let an incompatible union candidate provide contextual types; retain the name check and add a differing-keyword-name regression.
|
Verification: The relevant tests could not be fully run in the isolated environment; this review is not fully verified. |
| (!isPositionOnlyParam && param.d.name?.d.value === expectedParam.param.name); | ||
|
|
||
| // If the parameter category matches and both of the parameters are | ||
| // either separators (/ or *) or not separators, copy the type |
There was a problem hiding this comment.
Warning · Non-blocking recommendation
isPositionOnlyParam depends on paramsArePositionOnly, which is mutated during iteration and makes matching sensitive to loop ordering. Precompute each lambda parameter's positional-only status before contextual matching.
[verified]
|
Verification: The relevant tests could not be fully run in the isolated environment; this review is not fully verified. |
Fixes #11603.
When a lambda was contextually typed from a union of callable candidates, a keyword-only parameter from one candidate could incorrectly provide the type for a positional lambda parameter. This caused the wrong candidate to influence inference instead of being rejected.
Treat a keyword-only contextual parameter as compatible only with a lambda parameter that follows
*or*args. Otherwise, skip that candidate and continue with the remaining contextual signatures.Tests cover the reported case, generic and callback-protocol variants, ordinary callable unions, positional callable objects, and the incompatible keyword-only case.
Validation:
Lambda4: 1/1 passedtypeEvaluator1.test.ts: 159/159 passednpm run build:cli:devgit diff --check