Skip to content

Reject callable assignment when dest keyword form is incompatible - #11654

Open
Henry Su (hsusul) wants to merge 1 commit into
microsoft:mainfrom
hsusul:fix/callable-kwargs-keyword-form
Open

Reject callable assignment when dest keyword form is incompatible#11654
Henry Su (hsusul) wants to merge 1 commit into
microsoft:mainfrom
hsusul:fix/callable-kwargs-keyword-form

Conversation

@hsusul

Copy link
Copy Markdown
Contributor

Summary

  • A dest positional-or-keyword parameter can be passed by name, so callable assignability must check that form against the source **kwargs (or a same-named keyword-only parameter), not only against *args.
  • This rejects cases like (*args: int, **kwargs: bool) assigned to (a: int), which is invalid because the dest may be called as f(a=10).

Fixes #10237

Test plan

  • pnpm exec jest typeEvaluator2.test.ts -t "CallbackProtocol" --forceExit in packages/pyright-internal
  • pnpm exec jest typeEvaluator4.test.ts -t "FunctionAssignability" --forceExit

A dest positional-or-keyword parameter can be passed by name, so it must be compatible with the source keyword-only parameter or **kwargs, not only *args.
@heejaechang

Heejae Chang (heejaechang) commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

🔒 Automated review in progress — Heejae Chang (@heejaechang) is auto-reviewing this PR.

Comment thread packages/pyright-internal/src/analyzer/typeEvaluator.ts
@heejaechang Heejae Chang (heejaechang) added the review-auto:changes-requested Automated review: posted blocking findings to address. label Aug 18, 2026


def func3(cb: AcceptsKeywordOrPositional):
z: AcceptsKeywordOrPositional = ok_cb

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we add positive cases that exercise each newly accepted path? Assigning (*args: int, **kwargs: int) to (a: int) covers the new **kwargs fallback, and assigning (*args: int, a: int) to (a: int) covers the named keyword-only lookup. The current ok_cb case is an exact-signature assignment and would pass before this change.

@@ -28523,6 +28530,43 @@ export function createTypeEvaluator(
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This check needs to account for overload-overlap mode. For example, (a: int) -> int and (*args: int, **kwargs: bool) -> str still overlap for the positional call f(1), even though their keyword forms do not. During PartialOverloadOverlap, this unconditional failure makes assignFunction return false and suppresses the incompatible-return diagnostic. Could we model the positional and keyword paths disjunctively (or otherwise preserve an overlap once a shared positional call exists) and add a regression case to overloadOverlap1.py?

@crypt0lith

Copy link
Copy Markdown
Contributor

I opened #11670 for this same issue; it keeps the destParamInfo fallback from this PR (Henry Su (@hsusul) is co-authored on the commit), but the rest of #11670's implementation is fundamentally in conflict with this PR. It repairs the two existing sites where **kwargs-assignment previously bailed rather than using a separate sweep over dest params. And it gates the new failures on PartialOverloadOverlap per Rich Chiodo (@rchiodo)'s comment.

Opening another PR and flagging here seemed more straightforward than framing it as an addendum. Thank you for your time.

@github-actions

Copy link
Copy Markdown
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

sympy (https://github.com/sympy/sympy)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:314:30 - error: Cannot access attribute "as_independent" for class "Basic"
-     Attribute "as_independent" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:380:15 - error: Operator "*" not supported for "None" (reportOptionalOperand)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:383:13 - error: No overloads for "__init__" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:383:18 - error: Argument of type "list[Iterator[Unknown]]" cannot be assigned to parameter "iterable" of type "Iterable[list[bytes]]" in function "__init__" (reportArgumentType)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:383:30 - error: Cannot access attribute "as_independent" for class "Basic"
-     Attribute "as_independent" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:383:78 - error: "args" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/hypergeometric.py:247:67 - error: Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/ode/lie_group.py:619:61 - error: Operator "-" not supported for type "Unknown | Basic" (reportOperatorIssue)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:468:28 - error: Argument of type "Expr | Unknown | None" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
+   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:468:28 - error: Argument of type "Unknown | None" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
-     Type "Expr | Unknown | None" is not assignable to type "Expr"
+     Type "Unknown | None" is not assignable to type "Expr"
-   .../projects/sympy/sympy/solvers/ode/ode.py:1433:17 - error: Argument of type "One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | Expr" cannot be assigned to parameter "value" of type "Zero" in function "__setitem__"
+   .../projects/sympy/sympy/solvers/ode/ode.py:1433:17 - error: Argument of type "One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Unknown | Expr" cannot be assigned to parameter "value" of type "Zero" in function "__setitem__"
-     Type "One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | Expr" is not assignable to type "Zero"
+     Type "One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Unknown | Expr" is not assignable to type "Zero"
-   .../projects/sympy/sympy/solvers/ode/ode.py:1579:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1580:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1590:9 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1590:12 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1590:19 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1597:43 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1597:64 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1603:9 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1603:12 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1603:19 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1610:43 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1610:74 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1616:9 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1616:12 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1616:19 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1623:49 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1623:70 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1753:36 - error: Cannot access attribute "lhs" for class "Expr"
+     Attribute "lhs" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1753:55 - error: Cannot access attribute "rhs" for class "Expr"
+     Attribute "rhs" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1754:36 - error: Cannot access attribute "lhs" for class "Expr"
+     Attribute "lhs" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1755:17 - error: No overloads for "__setitem__" match the provided arguments (reportCallIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1755:17 - error: Argument of type "Equality | BooleanFalse | BooleanTrue | Unknown | Expr" cannot be assigned to parameter "value" of type "Equality | BooleanFalse | BooleanTrue" in function "__setitem__"
+     Type "Equality | BooleanFalse | BooleanTrue | Unknown | Expr" is not assignable to type "Equality | BooleanFalse | BooleanTrue"
+       Type "Expr" is not assignable to type "Equality | BooleanFalse | BooleanTrue"
+         "Expr" is not assignable to "Equality"
+         "Expr" is not assignable to "BooleanFalse"
+         "Expr" is not assignable to "BooleanTrue" (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:2961:17 - error: Argument of type "One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | Expr" cannot be assigned to parameter "value" of type "Zero" in function "__setitem__"
+   .../projects/sympy/sympy/solvers/ode/ode.py:2961:17 - error: Argument of type "One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Unknown | Expr" cannot be assigned to parameter "value" of type "Zero" in function "__setitem__"
-     Type "One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Infinity | NegativeInfinity | Float | Number | Expr" is not assignable to type "Zero"
+     Type "One | NegativeOne | Zero | Integer | NaN | ComplexInfinity | Rational | Unknown | Expr" is not assignable to type "Zero"
-   .../projects/sympy/sympy/solvers/ode/ode.py:3442:7 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3442:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3443:7 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3443:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3444:14 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3445:14 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3446:14 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3458:50 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3459:50 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3460:50 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3500:5 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3500:7 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)

... (truncated 402 lines) ...

xarray (https://github.com/pydata/xarray)
-       Parameter name mismatch: "objs" versus "draw"
+       Parameter name mismatch: "objs" versus "draw" (reportInconsistentOverload)
-       Extra parameter "objs" (reportInconsistentOverload)
-       Parameter name mismatch: "objs" versus "draw"
+       Parameter name mismatch: "objs" versus "draw" (reportInconsistentOverload)
-       Extra parameter "objs" (reportInconsistentOverload)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-auto:changes-requested Automated review: posted blocking findings to address.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

False negative for callable assignability

4 participants