Repository: quora/pyanalyze
Pull request #301 (2021-12-03 02:09:01)

improve signature compatibility


Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index e700ded..2cacc5d 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,7 @@
 
 ## Unreleased
 
+- Fix compatibility of signatures with extra parameters (#301)
 - Enhance `reveal_type()` output for `UnboundMethodValue` (#300)
 - Fix handling of `async for` (#298)
 - Add support for stub-only Protocols (#295)
diff --git a/pyanalyze/signature.py b/pyanalyze/signature.py
index 716f7b9..41b8154 100644
--- a/pyanalyze/signature.py
+++ b/pyanalyze/signature.py
@@ -1174,6 +1174,29 @@ class Signature:
                         )
                     tv_maps.append(tv_map)
 
+        for param in their_params:
+            if (
+                param.kind is SigParameter.VAR_POSITIONAL
+                or param.kind is SigParameter.VAR_KEYWORD
+            ):
+                continue  # ok if they have extra *args or **kwargs
+            elif param.default is not EMPTY:
+                continue
+            elif param.kind is SigParameter.POSITIONAL_ONLY:
+                if param.name not in consumed_positional:
+                    return CanAssignError(
+                        f"takes extra positional-only paramter {param.name!r}"
+                    )
+            elif param.kind is SigParameter.POSITIONAL_OR_KEYWORD:
+                if (
+                    param.name not in consumed_positional
+                    and param.name not in consumed_keyword
+                ):
+                    return CanAssignError(f"takes extra paramter {param.name!r}")
+            elif param.kind is SigParameter.KEYWORD_ONLY:
+                if param.name not in consumed_keyword:
+                    return CanAssignError(f"takes extra paramter {param.name!r}")
+
         return unify_typevar_maps(tv_maps)
 
     def get_param_of_kind(self, kind: inspect._ParameterKind) -> Optional[SigParameter]:


Test patch:
diff --git a/pyanalyze/test_signature.py b/pyanalyze/test_signature.py
index bf402f3..8e1d729 100644
--- a/pyanalyze/test_signature.py
+++ b/pyanalyze/test_signature.py
@@ -71,10 +71,20 @@ class TestCanAssign:
         pos_only_object = P("y", annotation=TypedValue(object), kind=P.POSITIONAL_ONLY)
         pos_only_bool = P("z", annotation=TypedValue(bool), kind=P.POSITIONAL_ONLY)
         pos_kw_int = P("a", annotation=TypedValue(int), kind=P.POSITIONAL_OR_KEYWORD)
+        pos_only_with_default = P(
+            "d",
+            annotation=TypedValue(int),
+            kind=P.POSITIONAL_ONLY,
+            default=KnownValue(0),
+        )
         pos_only_sig = Signature.make([pos_only_int])
         self.can(pos_only_sig, pos_only_sig)
         self.can(pos_only_sig, Signature.make([pos_kw_int]))
         self.can(pos_only_sig, Signature.make([pos_only_object]))
+        self.can(pos_only_sig, Signature.make([pos_only_with_default]))
+        self.can(pos_only_sig, Signature.make([pos_only_int, pos_only_with_default]))
+        self.cannot(Signature.make([pos_only_with_default]), pos_only_sig)
+        self.cannot(pos_only_sig, Signature.make([pos_only_int, pos_only_bool]))
         self.cannot(pos_only_sig, Signature.make([pos_only_bool]))
         self.cannot(
             pos_only_sig,
@@ -99,9 +109,17 @@ class TestCanAssign:
         )
         pos_kw_bool = P("a", annotation=TypedValue(bool), kind=P.POSITIONAL_OR_KEYWORD)
         pos_only_int = P("a", annotation=TypedValue(int), kind=P.POSITIONAL_ONLY)
+        pos_kw_with_default = P(
+            "d",
+            annotation=TypedValue(int),
+            kind=P.POSITIONAL_OR_KEYWORD,
+            default=KnownValue(0),
+        )
         pos_kw_sig = Signature.make([pos_kw_int])
         self.can(pos_kw_sig, pos_kw_sig)
         self.can(pos_kw_sig, Signature.make([pos_kw_object]))
+        self.can(pos_kw_sig, Signature.make([pos_kw_int, pos_kw_with_default]))
+        self.cannot(pos_kw_sig, Signature.make([pos_kw_int, pos_kw_int_b]))
         self.cannot(pos_kw_sig, Signature.make([pos_kw_bool]))
         self.cannot(pos_kw_sig, Signature.make([pos_only_int]))
         self.cannot(pos_kw_sig, Signature.make([pos_kw_int_b]))
@@ -190,7 +208,12 @@ class TestCanAssign:
             var_pos_sig,
             Signature.make(
                 [
-                    P("d", annotation=TypedValue(object), kind=P.POSITIONAL_ONLY),
+                    P(
+                        "d",
+                        annotation=TypedValue(object),
+                        kind=P.POSITIONAL_ONLY,
+                        default=KnownValue(True),
+                    ),
                     var_pos_int,
                 ]
             ),
@@ -199,7 +222,12 @@ class TestCanAssign:
             var_pos_sig,
             Signature.make(
                 [
-                    P("d", annotation=TypedValue(bool), kind=P.POSITIONAL_ONLY),
+                    P(
+                        "d",
+                        annotation=TypedValue(bool),
+                        kind=P.POSITIONAL_ONLY,
+                        default=KnownValue(True),
+                    ),
                     var_pos_int,
                 ]
             ),
@@ -216,13 +244,29 @@ class TestCanAssign:
         self.can(
             var_kw_sig,
             Signature.make(
-                [P("d", annotation=TypedValue(object), kind=P.KEYWORD_ONLY), var_kw_int]
+                [
+                    P(
+                        "d",
+                        annotation=TypedValue(object),
+                        kind=P.KEYWORD_ONLY,
+                        default=KnownValue(True),
+                    ),
+                    var_kw_int,
+                ]
             ),
         )
         self.cannot(
             var_kw_sig,
             Signature.make(
-                [P("d", annotation=TypedValue(bool), kind=P.KEYWORD_ONLY), var_kw_int]
+                [
+                    P(
+                        "d",
+                        annotation=TypedValue(bool),
+                        kind=P.KEYWORD_ONLY,
+                        default=KnownValue(True),
+                    ),
+                    var_kw_int,
+                ]
             ),
         )