Skip to content

Commit 1c55e8d

Browse files
gh-104050: Improve some typing around defaults and sentinel values (#104626)
- Convert `unspecified` and `unknown` to be members of a `Sentinels` enum, rather than instances of bespoke classes. - An enum feels more idiomatic here, and works better with type checkers. - Convert some `==` and `!=` checks for these values to identity checks, which are more idiomatic with sentinels. - _Don't_ do the same for `Null`, as this needs to be a distinct type due to its usage in `clinic.py`. - Use `object` as the annotation for `default` across `clinic.py`. `default` can be literally any object, so `object` is the correct annotation here. --- Co-authored-by: Erlend E. Aasland <erlend.aasland@protonmail.com>
1 parent 61027c0 commit 1c55e8d

File tree

1 file changed

+23
-18
lines changed

1 file changed

+23
-18
lines changed

Tools/clinic/clinic.py

+23-18
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import contextlib
1313
import copy
1414
import cpp
15+
import enum
1516
import functools
1617
import hashlib
1718
import inspect
@@ -28,7 +29,7 @@
2829

2930
from collections.abc import Callable
3031
from types import FunctionType, NoneType
31-
from typing import Any, NamedTuple, NoReturn, Literal, overload
32+
from typing import Any, Final, NamedTuple, NoReturn, Literal, overload
3233

3334
# TODO:
3435
#
@@ -58,25 +59,26 @@
5859
"return_value",
5960
}
6061

61-
class Unspecified:
62+
63+
class Sentinels(enum.Enum):
64+
unspecified = "unspecified"
65+
unknown = "unknown"
66+
6267
def __repr__(self) -> str:
63-
return '<Unspecified>'
68+
return f"<{self.value.capitalize()}>"
69+
6470

65-
unspecified = Unspecified()
71+
unspecified: Final = Sentinels.unspecified
72+
unknown: Final = Sentinels.unknown
6673

6774

75+
# This one needs to be a distinct class, unlike the other two
6876
class Null:
6977
def __repr__(self) -> str:
7078
return '<Null>'
7179

72-
NULL = Null()
73-
7480

75-
class Unknown:
76-
def __repr__(self) -> str:
77-
return '<Unknown>'
78-
79-
unknown = Unknown()
81+
NULL = Null()
8082

8183
sig_end_marker = '--'
8284

@@ -2600,7 +2602,7 @@ class CConverter(metaclass=CConverterAutoRegister):
26002602
# Or the magic value "unknown" if this value is a cannot be evaluated
26012603
# at Argument-Clinic-preprocessing time (but is presumed to be valid
26022604
# at runtime).
2603-
default: bool | Unspecified = unspecified
2605+
default: object = unspecified
26042606

26052607
# If not None, default must be isinstance() of this type.
26062608
# (You can also specify a tuple of types.)
@@ -2686,11 +2688,11 @@ def __init__(self,
26862688
name: str,
26872689
py_name: str,
26882690
function,
2689-
default=unspecified,
2691+
default: object = unspecified,
26902692
*, # Keyword only args:
26912693
c_default: str | None = None,
26922694
py_default: str | None = None,
2693-
annotation: str | Unspecified = unspecified,
2695+
annotation: str | Literal[Sentinels.unspecified] = unspecified,
26942696
unused: bool = False,
26952697
**kwargs
26962698
):
@@ -2699,7 +2701,10 @@ def __init__(self,
26992701
self.unused = unused
27002702

27012703
if default is not unspecified:
2702-
if self.default_type and not isinstance(default, (self.default_type, Unknown)):
2704+
if (self.default_type
2705+
and default is not unknown
2706+
and not isinstance(default, self.default_type)
2707+
):
27032708
if isinstance(self.default_type, type):
27042709
types_str = self.default_type.__name__
27052710
else:
@@ -2713,7 +2718,7 @@ def __init__(self,
27132718
if py_default:
27142719
self.py_default = py_default
27152720

2716-
if annotation != unspecified:
2721+
if annotation is not unspecified:
27172722
fail("The 'annotation' parameter is not currently permitted.")
27182723

27192724
# this is deliberate, to prevent you from caching information
@@ -3967,7 +3972,7 @@ class CReturnConverter(metaclass=CReturnConverterAutoRegister):
39673972

39683973
# The Python default value for this parameter, as a Python value.
39693974
# Or the magic value "unspecified" if there is no default.
3970-
default = None
3975+
default: object = None
39713976

39723977
def __init__(self, *, py_default=None, **kwargs):
39733978
self.py_default = py_default
@@ -4767,7 +4772,7 @@ def bad_node(self, node):
47674772
# but at least make an attempt at ensuring it's a valid expression.
47684773
try:
47694774
value = eval(default)
4770-
if value == unspecified:
4775+
if value is unspecified:
47714776
fail("'unspecified' is not a legal default value!")
47724777
except NameError:
47734778
pass # probably a named constant

0 commit comments

Comments
 (0)