Repository: quora/pyanalyze
Pull request #351 (2021-12-19 22:51:48)

SigParameter no longer inherits from inspect.Parameter


Patch:
diff --git a/pyanalyze/annotations.py b/pyanalyze/annotations.py
index e4d75c9..9fc8f08 100644
--- a/pyanalyze/annotations.py
+++ b/pyanalyze/annotations.py
@@ -57,7 +57,7 @@ from .extensions import (
     TypeGuard,
 )
 from .find_unused import used
-from .signature import SigParameter, Signature
+from .signature import SigParameter, Signature, ParameterKind
 from .safe import is_typing_name, is_instance_of_typing_name
 from .value import (
     AnnotatedValue,
@@ -422,7 +422,7 @@ def _type_from_runtime(val: Any, ctx: Context, is_typeddict: bool = False) -> Va
         params = [
             SigParameter(
                 f"__arg{i}",
-                kind=SigParameter.POSITIONAL_ONLY,
+                kind=ParameterKind.POSITIONAL_ONLY,
                 annotation=_type_from_runtime(arg, ctx),
             )
             for i, arg in enumerate(arg_types)
@@ -861,7 +861,7 @@ def _value_of_origin_args(
         params = [
             SigParameter(
                 f"__arg{i}",
-                kind=SigParameter.POSITIONAL_ONLY,
+                kind=ParameterKind.POSITIONAL_ONLY,
                 annotation=_type_from_runtime(arg, ctx, is_typeddict=True),
             )
             for i, arg in enumerate(arg_types)
@@ -959,7 +959,7 @@ def _make_callable_from_value(
         params = [
             SigParameter(
                 f"__arg{i}",
-                kind=SigParameter.POSITIONAL_ONLY,
+                kind=ParameterKind.POSITIONAL_ONLY,
                 annotation=_type_from_value(arg, ctx),
             )
             for i, arg in enumerate(args.members)
diff --git a/pyanalyze/arg_spec.py b/pyanalyze/arg_spec.py
index eb541f9..d9a5739 100644
--- a/pyanalyze/arg_spec.py
+++ b/pyanalyze/arg_spec.py
@@ -28,6 +28,7 @@ from .signature import (
     make_bound_method,
     SigParameter,
     Signature,
+    ParameterKind,
 )
 from .typeshed import TypeshedFinder
 from .value import (
@@ -91,8 +92,8 @@ def with_implementation(fn: object, implementation_fn: Impl) -> Iterable[None]:
             # builtin or something, just use a generic argspec
             argspec = Signature.make(
                 [
-                    SigParameter("args", SigParameter.VAR_POSITIONAL),
-                    SigParameter("kwargs", SigParameter.VAR_KEYWORD),
+                    SigParameter("args", ParameterKind.VAR_POSITIONAL),
+                    SigParameter("kwargs", ParameterKind.VAR_KEYWORD),
                 ],
                 callable=fn,
                 impl=implementation_fn,
@@ -237,12 +238,15 @@ class ArgSpecCache:
             typ = self._get_type_for_parameter(
                 parameter, func_globals, function_object, index
             )
-        if parameter.default is SigParameter.empty:
+        if parameter.default is inspect.Parameter.empty:
             default = None
         else:
             default = KnownValue(parameter.default)
         return SigParameter(
-            parameter.name, parameter.kind, default=default, annotation=typ
+            parameter.name,
+            ParameterKind(parameter.kind),
+            default=default,
+            annotation=typ,
         )
 
     def _get_type_for_parameter(
@@ -251,20 +255,20 @@ class ArgSpecCache:
         func_globals: Optional[Mapping[str, object]],
         function_object: Optional[object],
         index: int,
-    ) -> Optional[Value]:
-        if parameter.annotation is not SigParameter.empty:
+    ) -> Value:
+        if parameter.annotation is not inspect.Parameter.empty:
             typ = type_from_runtime(
                 parameter.annotation, ctx=AnnotationsContext(self, func_globals)
             )
-            if parameter.kind is SigParameter.VAR_POSITIONAL:
+            if parameter.kind is inspect.Parameter.VAR_POSITIONAL:
                 return GenericValue(tuple, [typ])
-            elif parameter.kind is SigParameter.VAR_KEYWORD:
+            elif parameter.kind is inspect.Parameter.VAR_KEYWORD:
                 return GenericValue(dict, [TypedValue(str), typ])
             return typ
         # If this is the self argument of a method, try to infer the self type.
         elif index == 0 and parameter.kind in (
-            SigParameter.POSITIONAL_ONLY,
-            SigParameter.POSITIONAL_OR_KEYWORD,
+            inspect.Parameter.POSITIONAL_ONLY,
+            inspect.Parameter.POSITIONAL_OR_KEYWORD,
         ):
             module_name = getattr(function_object, "__module__", None)
             qualname = getattr(function_object, "__qualname__", None)
@@ -293,14 +297,16 @@ class ArgSpecCache:
                         )
                     return TypedValue(class_obj)
         if parameter.kind in (
-            SigParameter.POSITIONAL_ONLY,
-            SigParameter.POSITIONAL_OR_KEYWORD,
-            SigParameter.KEYWORD_ONLY,
+            inspect.Parameter.POSITIONAL_ONLY,
+            inspect.Parameter.POSITIONAL_OR_KEYWORD,
+            inspect.Parameter.KEYWORD_ONLY,
         ):
-            return VariableNameValue.from_varname(
+            vnv = VariableNameValue.from_varname(
                 parameter.name, self.config.varname_value_map()
             )
-        return None
+            if vnv is not None:
+                return vnv
+        return AnyValue(AnySource.unannotated)
 
     def get_argspec(
         self, obj: object, impl: Optional[Impl] = None, is_asynq: bool = False
@@ -412,7 +418,7 @@ class ArgSpecCache:
                 [
                     SigParameter(
                         "x",
-                        SigParameter.POSITIONAL_ONLY,
+                        ParameterKind.POSITIONAL_ONLY,
                         annotation=type_from_runtime(
                             obj.__supertype__, ctx=self.default_context
                         ),
diff --git a/pyanalyze/implementation.py b/pyanalyze/implementation.py
index b4aa993..e171ead 100644
--- a/pyanalyze/implementation.py
+++ b/pyanalyze/implementation.py
@@ -13,7 +13,14 @@ from .stacked_scopes import (
     OrConstraint,
     VarnameWithOrigin,
 )
-from .signature import ANY_SIGNATURE, SigParameter, Signature, ImplReturn, CallContext
+from .signature import (
+    ANY_SIGNATURE,
+    SigParameter,
+    Signature,
+    ImplReturn,
+    CallContext,
+    ParameterKind,
+)
 from .value import (
     UNINITIALIZED_VALUE,
     AnnotatedValue,
@@ -1086,7 +1093,7 @@ def _len_impl(ctx: CallContext) -> ImplReturn:
     return ImplReturn(len_of_value(ctx.vars["obj"]), constraint)
 
 
-_POS_ONLY = SigParameter.POSITIONAL_ONLY
+_POS_ONLY = ParameterKind.POSITIONAL_ONLY
 _ENCODING_PARAMETER = SigParameter(
     "encoding", annotation=TypedValue(str), default=KnownValue("")
 )
@@ -1104,7 +1111,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
                 SigParameter("value", annotation=TypedValue(Value)),
                 SigParameter(
                     "skip_annotated",
-                    SigParameter.KEYWORD_ONLY,
+                    ParameterKind.KEYWORD_ONLY,
                     default=KnownValue(False),
                     annotation=TypedValue(bool),
                 ),
@@ -1315,7 +1322,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             [
                 SigParameter("self", _POS_ONLY, annotation=TypedValue(dict)),
                 SigParameter("m", _POS_ONLY, default=_NO_ARG_SENTINEL),
-                SigParameter("kwargs", SigParameter.VAR_KEYWORD),
+                SigParameter("kwargs", ParameterKind.VAR_KEYWORD),
             ],
             KnownValue(None),
             callable=dict.update,
@@ -1395,8 +1402,8 @@ def get_default_argspecs() -> Dict[object, Signature]:
         Signature.make(
             [
                 SigParameter("self", _POS_ONLY, annotation=TypedValue(str)),
-                SigParameter("args", SigParameter.VAR_POSITIONAL),
-                SigParameter("kwargs", SigParameter.VAR_KEYWORD),
+                SigParameter("args", ParameterKind.VAR_POSITIONAL),
+                SigParameter("kwargs", ParameterKind.VAR_KEYWORD),
             ],
             impl=_str_format_impl,
             callable=str.format,
@@ -1460,7 +1467,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             [
                 SigParameter(
                     "obj",
-                    SigParameter.POSITIONAL_ONLY,
+                    ParameterKind.POSITIONAL_ONLY,
                     annotation=TypedValue(collections.abc.Sized),
                 )
             ],
@@ -1478,15 +1485,15 @@ def get_default_argspecs() -> Dict[object, Signature]:
             [
                 SigParameter(
                     "iterable",
-                    SigParameter.POSITIONAL_ONLY,
+                    ParameterKind.POSITIONAL_ONLY,
                     annotation=TypedValue(collections.abc.Iterable),
                 ),
                 SigParameter(
                     "key",
-                    SigParameter.KEYWORD_ONLY,
+                    ParameterKind.KEYWORD_ONLY,
                     annotation=CallableValue(
                         Signature.make(
-                            [SigParameter("arg", SigParameter.POSITIONAL_ONLY)],
+                            [SigParameter("arg", ParameterKind.POSITIONAL_ONLY)],
                             return_annotation=TypedValue("_typeshed.SupportsLessThan"),
                         )
                     ),
@@ -1494,7 +1501,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
                 ),
                 SigParameter(
                     "reverse",
-                    SigParameter.KEYWORD_ONLY,
+                    ParameterKind.KEYWORD_ONLY,
                     annotation=TypedValue(bool),
                     default=KnownValue(False),
                 ),
@@ -1507,7 +1514,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
         Signature.make(
             [
                 SigParameter(
-                    "obj", SigParameter.POSITIONAL_ONLY, annotation=TypedValue(object)
+                    "obj", ParameterKind.POSITIONAL_ONLY, annotation=TypedValue(object)
                 )
             ],
             callable=callable,
@@ -1520,7 +1527,7 @@ def get_default_argspecs() -> Dict[object, Signature]:
             [
                 SigParameter(
                     "object",
-                    SigParameter.POSITIONAL_OR_KEYWORD,
+                    ParameterKind.POSITIONAL_OR_KEYWORD,
                     annotation=TypedValue(object),
                 )
             ],
diff --git a/pyanalyze/signature.py b/pyanalyze/signature.py
index e7201ec..919b8ac 100644
--- a/pyanalyze/signature.py
+++ b/pyanalyze/signature.py
@@ -56,6 +56,7 @@ import asynq
 from collections import defaultdict
 import collections.abc
 from dataclasses import dataclass, field, replace
+import enum
 import itertools
 from types import MethodType, FunctionType
 import inspect
@@ -83,7 +84,7 @@ if TYPE_CHECKING:
     from .name_check_visitor import NameCheckVisitor
 
 EMPTY = inspect.Parameter.empty
-
+UNANNOTATED = AnyValue(AnySource.unannotated)
 ARGS = qcore.MarkerObject("*args")
 KWARGS = qcore.MarkerObject("**kwargs")
 
@@ -224,64 +225,81 @@ class CallContext:
 Impl = Callable[[CallContext], Union[Value, ImplReturn]]
 
 
-class SigParameter(inspect.Parameter):
-    """Wrapper around :class:`inspect.Parameter` that stores annotations
-    as :class:`pyanalyze.value.Value` objects."""
+class ParameterKind(enum.Enum):
+    """Kinds of parameters."""
 
-    __slots__ = ()
+    # Values must match inspect._ParameterKind
+    POSITIONAL_ONLY = 0
+    POSITIONAL_OR_KEYWORD = 1
+    VAR_POSITIONAL = 2
+    KEYWORD_ONLY = 3
+    VAR_KEYWORD = 4
 
-    def __init__(
-        self,
-        name: str,
-        kind: inspect._ParameterKind = inspect.Parameter.POSITIONAL_OR_KEYWORD,
-        *,
-        default: Union[None, Value, Literal[EMPTY]] = None,
-        annotation: Union[None, Value, Literal[EMPTY]] = None,
-    ) -> None:
-        if default is None:
-            default_composite = EMPTY
-        elif isinstance(default, Value):
-            default_composite = Composite(default, None, None)
-        else:
-            default_composite = default
-        if annotation is None:
-            annotation = EMPTY
-        super().__init__(name, kind, default=default_composite, annotation=annotation)
+
+@dataclass
+class SigParameter:
+    """Represents a single parameter to a callable."""
+
+    name: str
+    """Name of the parameter."""
+    kind: ParameterKind = ParameterKind.POSITIONAL_OR_KEYWORD
+    """How the parameter can be passed."""
+    default: Optional[Value] = None
+    """The default for the parameter, or EMPTY if there is no default."""
+    annotation: Value = AnyValue(AnySource.unannotated)
+    """Type annotation for the parameter."""
+
+    # For compatibility
+    empty: ClassVar[Literal[EMPTY]] = EMPTY
+    POSITIONAL_ONLY: ClassVar[
+        Literal[ParameterKind.POSITIONAL_ONLY]
+    ] = ParameterKind.POSITIONAL_ONLY
+    POSITIONAL_OR_KEYWORD: ClassVar[
+        Literal[ParameterKind.POSITIONAL_OR_KEYWORD]
+    ] = ParameterKind.POSITIONAL_OR_KEYWORD
+    VAR_POSITIONAL: ClassVar[
+        Literal[ParameterKind.VAR_POSITIONAL]
+    ] = ParameterKind.VAR_POSITIONAL
+    KEYWORD_ONLY: ClassVar[
+        Literal[ParameterKind.KEYWORD_ONLY]
+    ] = ParameterKind.KEYWORD_ONLY
+    VAR_KEYWORD: ClassVar[
+        Literal[ParameterKind.VAR_KEYWORD]
+    ] = ParameterKind.VAR_KEYWORD
+
+    def __post_init__(self) -> None:
+        # backward compatibility
+        if self.default is EMPTY:
+            self.default = None
 
     def substitute_typevars(self, typevars: TypeVarMap) -> "SigParameter":
-        if self._annotation is EMPTY:
-            annotation = self._annotation
-        else:
-            annotation = self._annotation.substitute_typevars(typevars)
         return SigParameter(
-            name=self._name,
-            kind=self._kind,
-            default=self._default,
-            annotation=annotation,
+            name=self.name,
+            kind=self.kind,
+            default=self.default,
+            annotation=self.annotation.substitute_typevars(typevars),
         )
 
     def get_annotation(self) -> Value:
-        if self.annotation is EMPTY:
-            return AnyValue(AnySource.unannotated)
         return self.annotation
 
     def __str__(self) -> str:
         # Adapted from Parameter.__str__
         kind = self.kind
-        formatted = self._name
+        formatted = self.name
 
-        if self._annotation is not EMPTY:
-            formatted = f"{formatted}: {self._annotation}"
+        if self.annotation != UNANNOTATED:
+            formatted = f"{formatted}: {self.annotation}"
 
-        if self._default is not EMPTY:
-            if self._annotation is not EMPTY:
-                formatted = f"{formatted} = {self._default.value}"
+        if self.default is not None:
+            if self.annotation != UNANNOTATED:
+                formatted = f"{formatted} = {self.default}"
             else:
-                formatted = f"{formatted}={self._default.value}"
+                formatted = f"{formatted}={self.default}"
 
-        if kind is SigParameter.VAR_POSITIONAL:
+        if kind is ParameterKind.VAR_POSITIONAL:
             formatted = "*" + formatted
-        elif kind is SigParameter.VAR_KEYWORD:
+        elif kind is ParameterKind.VAR_KEYWORD:
             formatted = "**" + formatted
 
         return formatted
@@ -323,8 +341,6 @@ class Signature:
 
     def __post_init__(self) -> None:
         for param_name, param in self.parameters.items():
-            if param.annotation is EMPTY:
-                continue
             typevars = list(extract_typevars(param.annotation))
             if typevars:
                 self.typevars_of_params[param_name] = typevars
@@ -356,10 +372,7 @@ class Signature:
         - A Value or None, used for union decomposition with overloads.
 
         """
-        if param.annotation is not EMPTY and not (
-            isinstance(param.default, Composite)
-            and composite.value is param.default.value
-        ):
+        if param.annotation != UNANNOTATED and composite.value is not param.default:
             if typevar_map:
                 param_typ = param.annotation.substitute_typevars(typevar_map)
             else:
@@ -411,9 +424,9 @@ class Signature:
     def _get_positional_parameter(self, index: int) -> Optional[SigParameter]:
         for i, param in enumerate(self.parameters.values()):
             if param.kind in (
-                SigParameter.VAR_KEYWORD,
-                SigParameter.VAR_POSITIONAL,
-                SigParameter.KEYWORD_ONLY,
+                ParameterKind.VAR_KEYWORD,
+                ParameterKind.VAR_POSITIONAL,
+                ParameterKind.KEYWORD_ONLY,
             ):
                 return None
             if i == index:
@@ -497,7 +510,7 @@ class Signature:
         star_kwargs_consumed = False
 
         for param in self.parameters.values():
-            if param.kind is SigParameter.POSITIONAL_ONLY:
+            if param.kind is ParameterKind.POSITIONAL_ONLY:
                 if positional_index < len(actual_args.positionals):
                     bound_args[param.name] = (
                         positional_index,
@@ -507,7 +520,7 @@ class Signature:
                 elif actual_args.star_args is not None:
                     bound_args[param.name] = None, Composite(actual_args.star_args)
                     star_args_consumed = True
-                elif param.default is EMPTY:
+                elif param.default is None:
                     self.show_call_error(
                         f"Missing required positional argument: '{param.name}'",
                         node,
@@ -515,8 +528,8 @@ class Signature:
                     )
                     return None
                 else:
-                    bound_args[param.name] = None, param.default
-            elif param.kind is SigParameter.POSITIONAL_OR_KEYWORD:
+                    bound_args[param.name] = None, Composite(param.default)
+            elif param.kind is ParameterKind.POSITIONAL_OR_KEYWORD:
                 if positional_index < len(actual_args.positionals):
                     bound_args[param.name] = (
                         positional_index,
@@ -552,7 +565,7 @@ class Signature:
                     bound_args[param.name] = None, Composite(value)
                 elif param.name in actual_args.keywords:
                     definitely_provided, composite = actual_args.keywords[param.name]
-                    if not definitely_provided and param.default is EMPTY:
+                    if not definitely_provided and param.default is None:
                         self.show_call_error(
                             f"Parameter '{param.name}' may not be provided by this"
                             " call",
@@ -565,17 +578,17 @@ class Signature:
                 elif actual_args.star_kwargs is not None:
                     bound_args[param.name] = None, Composite(actual_args.star_kwargs)
                     star_kwargs_consumed = True
-                elif param.default is EMPTY:
+                elif param.default is None:
                     self.show_call_error(
                         f"Missing required argument: '{param.name}'", node, visitor
                     )
                     return None
                 else:
-                    bound_args[param.name] = None, param.default
-            elif param.kind is SigParameter.KEYWORD_ONLY:
+                    bound_args[param.name] = None, Composite(param.default)
+            elif param.kind is ParameterKind.KEYWORD_ONLY:
                 if param.name in actual_args.keywords:
                     definitely_provided, composite = actual_args.keywords[param.name]
-                    if not definitely_provided and param.default is EMPTY:
+                    if not definitely_provided and param.default is None:
                         self.show_call_error(
                             f"Parameter '{param.name}' may not be provided by this"
                             " call",
@@ -589,14 +602,14 @@ class Signature:
                     bound_args[param.name] = None, Composite(actual_args.star_kwargs)
                     star_kwargs_consumed = True
                     keywords_consumed.add(param.name)
-                elif param.default is EMPTY:
+                elif param.default is None:
                     self.show_call_error(
                         f"Missing required argument: '{param.name}'", node, visitor
                     )
                     return None
                 else:
-                    bound_args[param.name] = None, param.default
-            elif param.kind is SigParameter.VAR_POSITIONAL:
+                    bound_args[param.name] = None, Composite(param.default)
+            elif param.kind is ParameterKind.VAR_POSITIONAL:
                 star_args_consumed = True
                 positionals = []
                 while positional_index < len(actual_args.positionals):
@@ -608,7 +621,7 @@ class Signature:
                 else:
                     star_args_value = SequenceIncompleteValue(tuple, positionals)
                 bound_args[param.name] = None, Composite(star_args_value)
-            elif param.kind is SigParameter.VAR_KEYWORD:
+            elif param.kind is ParameterKind.VAR_KEYWORD:
                 star_kwargs_consumed = True
                 items = {}
                 for key, (
@@ -798,10 +811,7 @@ class Signature:
                     )
                 else:
                     return_value = runtime_return
-        if return_value is EMPTY:
-            ret = ImplReturn(AnyValue(AnySource.unannotated), is_error=had_error)
-        else:
-            ret = self._apply_annotated_constraints(return_value, composites)
+        ret = self._apply_annotated_constraints(return_value, composites)
         return ret._replace(
             is_error=had_error,
             used_any_for_match=used_any,
@@ -895,14 +905,14 @@ class Signature:
             return return_tv_map
         tv_maps = [return_tv_map]
         their_params = list(other.parameters.values())
-        their_args = other.get_param_of_kind(SigParameter.VAR_POSITIONAL)
+        their_args = other.get_param_of_kind(ParameterKind.VAR_POSITIONAL)
         if their_args is not None:
             their_args_index = their_params.index(their_args)
             args_annotation = their_args.get_annotation()
         else:
             their_args_index = -1
             args_annotation = None
-        their_kwargs = other.get_param_of_kind(SigParameter.VAR_KEYWORD)
+        their_kwargs = other.get_param_of_kind(ParameterKind.VAR_KEYWORD)
         if their_kwargs is not None:
             kwargs_annotation = their_kwargs.get_annotation()
         else:
@@ -911,15 +921,12 @@ class Signature:
         consumed_keyword = set()
         for i, my_param in enumerate(self.parameters.values()):
             my_annotation = my_param.get_annotation()
-            if my_param.kind is SigParameter.POSITIONAL_ONLY:
+            if my_param.kind is ParameterKind.POSITIONAL_ONLY:
                 if i < len(their_params) and their_params[i].kind in (
-                    SigParameter.POSITIONAL_ONLY,
-                    SigParameter.POSITIONAL_OR_KEYWORD,
+                    ParameterKind.POSITIONAL_ONLY,
+                    ParameterKind.POSITIONAL_OR_KEYWORD,
                 ):
-                    if (
-                        my_param.default is not EMPTY
-                        and their_params[i].default is EMPTY
-                    ):
+                    if my_param.default is not None and their_params[i].default is None:
                         return CanAssignError(
                             f"positional-only param {my_param.name!r} has no default"
                         )
@@ -945,20 +952,17 @@ class Signature:
                     return CanAssignError(
                         f"positional-only parameter {i} is not accepted"
                     )
-            elif my_param.kind is SigParameter.POSITIONAL_OR_KEYWORD:
+            elif my_param.kind is ParameterKind.POSITIONAL_OR_KEYWORD:
                 if (
                     i < len(their_params)
-                    and their_params[i].kind is SigParameter.POSITIONAL_OR_KEYWORD
+                    and their_params[i].kind is ParameterKind.POSITIONAL_OR_KEYWORD
                 ):
                     if my_param.name != their_params[i].name:
                         return CanAssignError(
                             f"param name {their_params[i].name!r} does not match"
                             f" {my_param.name!r}"
                         )
-                    if (
-                        my_param.default is not EMPTY
-                        and their_params[i].default is EMPTY
-                    ):
+                    if my_param.default is not None and their_params[i].default is None:
                         return CanAssignError(f"param {my_param.name!r} has no default")
                     their_annotation = their_params[i].get_annotation()
                     tv_map = their_annotation.can_assign(my_annotation, ctx)
@@ -972,7 +976,7 @@ class Signature:
                     consumed_keyword.add(their_params[i].name)
                 elif (
                     i < len(their_params)
-                    and their_params[i].kind is SigParameter.POSITIONAL_ONLY
+                    and their_params[i].kind is ParameterKind.POSITIONAL_ONLY
                 ):
                     return CanAssignError(
                         f"parameter {my_param.name!r} is not accepted as a keyword"
@@ -995,13 +999,13 @@ class Signature:
                     return CanAssignError(
                         f"parameter {my_param.name!r} is not accepted"
                     )
-            elif my_param.kind is SigParameter.KEYWORD_ONLY:
+            elif my_param.kind is ParameterKind.KEYWORD_ONLY:
                 their_param = other.parameters.get(my_param.name)
                 if their_param is not None and their_param.kind in (
-                    SigParameter.POSITIONAL_OR_KEYWORD,
-                    SigParameter.KEYWORD_ONLY,
+                    ParameterKind.POSITIONAL_OR_KEYWORD,
+                    ParameterKind.KEYWORD_ONLY,
                 ):
-                    if my_param.default is not EMPTY and their_param.default is EMPTY:
+                    if my_param.default is not None and their_param.default is None:
                         return CanAssignError(
                             f"keyword-only param {my_param.name!r} has no default"
                         )
@@ -1025,7 +1029,7 @@ class Signature:
                     return CanAssignError(
                         f"parameter {my_param.name!r} is not accepted"
                     )
-            elif my_param.kind is SigParameter.VAR_POSITIONAL:
+            elif my_param.kind is ParameterKind.VAR_POSITIONAL:
                 if args_annotation is None:
                     return CanAssignError("*args are not accepted")
                 tv_map = args_annotation.can_assign(my_annotation, ctx)
@@ -1038,8 +1042,8 @@ class Signature:
                     if param.name not in consumed_positional
                     and param.kind
                     in (
-                        SigParameter.POSITIONAL_ONLY,
-                        SigParameter.POSITIONAL_OR_KEYWORD,
+                        ParameterKind.POSITIONAL_ONLY,
+                        ParameterKind.POSITIONAL_OR_KEYWORD,
                     )
                 ]
                 for extra_param in extra_positional:
@@ -1051,7 +1055,7 @@ class Signature:
                             [tv_map],
                         )
                     tv_maps.append(tv_map)
-            elif my_param.kind is SigParameter.VAR_KEYWORD:
+            elif my_param.kind is ParameterKind.VAR_KEYWORD:
                 if kwargs_annotation is None:
                     return CanAssignError("**kwargs are not accepted")
                 tv_map = kwargs_annotation.can_assign(my_annotation, ctx)
@@ -1063,7 +1067,7 @@ class Signature:
                     for param in their_params
                     if param.name not in consumed_keyword
                     and param.kind
-                    in (SigParameter.KEYWORD_ONLY, SigParameter.POSITIONAL_OR_KEYWORD)
+                    in (ParameterKind.KEYWORD_ONLY, ParameterKind.POSITIONAL_OR_KEYWORD)
                 ]
                 for extra_param in extra_keyword:
                     tv_map = extra_param.get_annotation().can_assign(my_annotation, ctx)
@@ -1077,30 +1081,30 @@ class Signature:
 
         for param in their_params:
             if (
-                param.kind is SigParameter.VAR_POSITIONAL
-                or param.kind is SigParameter.VAR_KEYWORD
+                param.kind is ParameterKind.VAR_POSITIONAL
+                or param.kind is ParameterKind.VAR_KEYWORD
             ):
                 continue  # ok if they have extra *args or **kwargs
-            elif param.default is not EMPTY:
+            elif param.default is not None:
                 continue
-            elif param.kind is SigParameter.POSITIONAL_ONLY:
+            elif param.kind is ParameterKind.POSITIONAL_ONLY:
                 if param.name not in consumed_positional:
                     return CanAssignError(
                         f"takes extra positional-only parameter {param.name!r}"
                     )
-            elif param.kind is SigParameter.POSITIONAL_OR_KEYWORD:
+            elif param.kind is ParameterKind.POSITIONAL_OR_KEYWORD:
                 if (
                     param.name not in consumed_positional
                     and param.name not in consumed_keyword
                 ):
                     return CanAssignError(f"takes extra parameter {param.name!r}")
-            elif param.kind is SigParameter.KEYWORD_ONLY:
+            elif param.kind is ParameterKind.KEYWORD_ONLY:
                 if param.name not in consumed_keyword:
                     return CanAssignError(f"takes extra parameter {param.name!r}")
 
         return unify_typevar_maps(tv_maps)
 
-    def get_param_of_kind(self, kind: inspect._ParameterKind) -> Optional[SigParameter]:
+    def get_param_of_kind(self, kind: ParameterKind) -> Optional[SigParameter]:
         for param in self.parameters.values():
             if param.kind is kind:
                 return param
@@ -1124,8 +1128,7 @@ class Signature:
     def walk_values(self) -> Iterable[Value]:
         yield from self.return_value.walk_values()
         for param in self.parameters.values():
-            if param.annotation is not EMPTY:
-                yield from param.annotation.walk_values()
+            yield from param.annotation.walk_values()
 
     def get_asynq_value(self) -> "Signature":
         """Return the :class:`Signature` for the `.asynq` attribute of an
@@ -1196,15 +1199,15 @@ class Signature:
 
             kind = param.kind
 
-            if kind == SigParameter.POSITIONAL_ONLY:
+            if kind == ParameterKind.POSITIONAL_ONLY:
                 render_pos_only_separator = True
             elif render_pos_only_separator:
                 yield "/"
                 render_pos_only_separator = False
 
-            if kind == SigParameter.VAR_POSITIONAL:
+            if kind == ParameterKind.VAR_POSITIONAL:
                 render_kw_only_separator = False
-            elif kind == SigParameter.KEYWORD_ONLY and render_kw_only_separator:
+            elif kind == ParameterKind.KEYWORD_ONLY and render_kw_only_separator:
                 yield "*"
                 render_kw_only_separator = False
 
@@ -1218,8 +1221,8 @@ class Signature:
             return ANY_SIGNATURE
         params = list(self.parameters.values())
         if not params or params[0].kind not in (
-            SigParameter.POSITIONAL_ONLY,
-            SigParameter.POSITIONAL_OR_KEYWORD,
+            ParameterKind.POSITIONAL_ONLY,
+            ParameterKind.POSITIONAL_OR_KEYWORD,
         ):
             return None
         return Signature(
diff --git a/pyanalyze/typeshed.py b/pyanalyze/typeshed.py
index fedfc0c..3e0420f 100644
--- a/pyanalyze/typeshed.py
+++ b/pyanalyze/typeshed.py
@@ -8,7 +8,13 @@ from .annotations import Context, type_from_value, value_from_ast
 from .error_code import ErrorCode
 from .safe import is_typing_name
 from .stacked_scopes import uniq_chain
-from .signature import ConcreteSignature, OverloadedSignature, SigParameter, Signature
+from .signature import (
+    ConcreteSignature,
+    OverloadedSignature,
+    SigParameter,
+    Signature,
+    ParameterKind,
+)
 from .value import (
     AnySource,
     AnyValue,
@@ -33,7 +39,7 @@ from collections.abc import (
     Sized,
 )
 from contextlib import AbstractContextManager
-from dataclasses import dataclass, field
+from dataclasses import dataclass, field, replace
 import collections.abc
 import qcore
 import inspect
@@ -658,7 +664,7 @@ class TypeshedFinder:
         defaults = [None] * num_without_defaults + args.defaults
         arguments = list(
             self._parse_param_list(
-                args.args, defaults, mod, SigParameter.POSITIONAL_OR_KEYWORD, objclass
+                args.args, defaults, mod, ParameterKind.POSITIONAL_OR_KEYWORD, objclass
             )
         )
         if autobind:
@@ -667,27 +673,27 @@ class TypeshedFinder:
 
         if args.vararg is not None:
             vararg_param = self._parse_param(
-                args.vararg, None, mod, SigParameter.VAR_POSITIONAL
+                args.vararg, None, mod, ParameterKind.VAR_POSITIONAL
             )
             annotation = GenericValue(tuple, [vararg_param.annotation])
-            arguments.append(vararg_param.replace(annotation=annotation))
+            arguments.append(replace(vararg_param, annotation=annotation))
         arguments += self._parse_param_list(
-            args.kwonlyargs, args.kw_defaults, mod, SigParameter.KEYWORD_ONLY
+            args.kwonlyargs, args.kw_defaults, mod, ParameterKind.KEYWORD_ONLY
         )
         if args.kwarg is not None:
             kwarg_param = self._parse_param(
-                args.kwarg, None, mod, SigParameter.VAR_KEYWORD
+                args.kwarg, None, mod, ParameterKind.VAR_KEYWORD
             )
             annotation = GenericValue(dict, [TypedValue(str), kwarg_param.annotation])
-            arguments.append(kwarg_param.replace(annotation=annotation))
+            arguments.append(replace(kwarg_param, annotation=annotation))
         # some typeshed types have a positional-only after a normal argument,
         # and Signature doesn't like that
         seen_non_positional = False
         cleaned_arguments = []
         for arg in arguments:
-            if arg.kind is SigParameter.POSITIONAL_ONLY and seen_non_positional:
+            if arg.kind is ParameterKind.POSITIONAL_ONLY and seen_non_positional:
                 cleaned_arguments = [
-                    arg.replace(kind=SigParameter.POSITIONAL_ONLY)
+                    replace(arg, kind=ParameterKind.POSITIONAL_ONLY)
                     for arg in cleaned_arguments
                 ]
                 seen_non_positional = False
@@ -708,7 +714,7 @@ class TypeshedFinder:
         args: Iterable[ast3.arg],
         defaults: Iterable[Optional[ast3.AST]],
         module: str,
-        kind: inspect._ParameterKind,
+        kind: ParameterKind,
         objclass: Optional[type] = None,
     ) -> Iterable[SigParameter]:
         for i, (arg, default) in enumerate(zip(args, defaults)):
@@ -721,7 +727,7 @@ class TypeshedFinder:
         arg: ast3.arg,
         default: Optional[ast3.arg],
         module: str,
-        kind: inspect._ParameterKind,
+        kind: ParameterKind,
         objclass: Optional[type] = None,
     ) -> SigParameter:
         typ = AnyValue(AnySource.unannotated)
@@ -740,13 +746,13 @@ class TypeshedFinder:
 
         name = arg.arg
         # Arguments that start with __ are positional-only in typeshed
-        if kind is SigParameter.POSITIONAL_OR_KEYWORD and name.startswith("__"):
-            kind = SigParameter.POSITIONAL_ONLY
+        if kind is ParameterKind.POSITIONAL_OR_KEYWORD and name.startswith("__"):
+            kind = ParameterKind.POSITIONAL_ONLY
             name = name[2:]
         # Mark self as positional-only. objclass should be given only if we believe
         # it's the "self" parameter.
         if objclass is not None:
-            kind = SigParameter.POSITIONAL_ONLY
+            kind = ParameterKind.POSITIONAL_ONLY
         if default is None:
             return SigParameter(name, kind, annotation=typ)
         else:


Test patch:
diff --git a/pyanalyze/test_arg_spec.py b/pyanalyze/test_arg_spec.py
index 6555ab8..6b27098 100644
--- a/pyanalyze/test_arg_spec.py
+++ b/pyanalyze/test_arg_spec.py
@@ -9,7 +9,7 @@ from .test_name_check_visitor import (
     ConfiguredNameCheckVisitor,
 )
 from .test_node_visitor import assert_passes
-from .signature import SigParameter, BoundMethodSignature, Signature
+from .signature import SigParameter, BoundMethodSignature, Signature, ParameterKind
 from .stacked_scopes import Composite
 from .arg_spec import ArgSpecCache, is_dot_asynq_function
 from .tests import l0cached_async_fn
@@ -122,8 +122,8 @@ def test_get_argspec():
             [
                 SigParameter("capybara"),
                 SigParameter("hutia", default=KnownValue(3)),
-                SigParameter("tucotucos", SigParameter.VAR_POSITIONAL),
-                SigParameter("proechimys", SigParameter.VAR_KEYWORD),
+                SigParameter("tucotucos", ParameterKind.VAR_POSITIONAL),
+                SigParameter("proechimys", ParameterKind.VAR_KEYWORD),
             ],
             callable=function,
         ) == ArgSpecCache(config).get_argspec(function)
@@ -227,12 +227,12 @@ def test_get_argspec():
             [
                 SigParameter(
                     "args",
-                    SigParameter.VAR_POSITIONAL,
+                    ParameterKind.VAR_POSITIONAL,
                     annotation=AnyValue(AnySource.inference),
                 ),
                 SigParameter(
                     "kwargs",
-                    SigParameter.VAR_KEYWORD,
+                    ParameterKind.VAR_KEYWORD,
                     annotation=AnyValue(AnySource.inference),
                 ),
             ],
@@ -241,7 +241,7 @@ def test_get_argspec():
         assert Signature.make(
             [
                 SigParameter(
-                    "x", SigParameter.POSITIONAL_ONLY, annotation=TypedValue(int)
+                    "x", ParameterKind.POSITIONAL_ONLY, annotation=TypedValue(int)
                 )
             ],
             NewTypeValue(NT),
diff --git a/pyanalyze/test_config.py b/pyanalyze/test_config.py
index 356e0e0..71b85f3 100644
--- a/pyanalyze/test_config.py
+++ b/pyanalyze/test_config.py
@@ -12,6 +12,7 @@ from .signature import (
     OverloadedSignature,
     Signature,
     SigParameter,
+    ParameterKind,
 )
 from .config import Config
 from . import tests
@@ -55,11 +56,11 @@ class TestConfig(Config):
         if issubclass(cls, tests.KeywordOnlyArguments):
             return Signature.make(
                 [
-                    SigParameter("self", kind=SigParameter.POSITIONAL_ONLY),
-                    SigParameter("args", kind=SigParameter.VAR_POSITIONAL),
+                    SigParameter("self", kind=ParameterKind.POSITIONAL_ONLY),
+                    SigParameter("args", kind=ParameterKind.VAR_POSITIONAL),
                     SigParameter(
                         "kwonly_arg",
-                        kind=SigParameter.KEYWORD_ONLY,
+                        kind=ParameterKind.KEYWORD_ONLY,
                         default=value.KnownValue(None),
                     ),
                 ],
@@ -80,7 +81,7 @@ class TestConfig(Config):
                     SigParameter("a"),
                     SigParameter(
                         "kwonly_arg",
-                        SigParameter.KEYWORD_ONLY,
+                        ParameterKind.KEYWORD_ONLY,
                         annotation=value.TypedValue(bool),
                     ),
                 ],
@@ -96,7 +97,7 @@ class TestConfig(Config):
                         [
                             SigParameter(
                                 "x",
-                                SigParameter.POSITIONAL_ONLY,
+                                ParameterKind.POSITIONAL_ONLY,
                                 annotation=value.TypedValue(str),
                             )
                         ],
diff --git a/pyanalyze/test_signature.py b/pyanalyze/test_signature.py
index b59414d..3e2fa28 100644
--- a/pyanalyze/test_signature.py
+++ b/pyanalyze/test_signature.py
@@ -7,7 +7,6 @@ from .value import (
     AnySource,
     AnyValue,
     CanAssignError,
-    ConstraintExtension,
     GenericValue,
     KnownValue,
     MultiValuedValue,