Skip to content

Commit a05e4a7

Browse files
authored
Merge branch 'master' into fix-enum-truthyness
2 parents 37dbd40 + fe15ee6 commit a05e4a7

File tree

12 files changed

+224
-19
lines changed

12 files changed

+224
-19
lines changed

mypy/checker.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,8 @@ def extract_callable_type(self, inner_type: Type | None, ctx: Context) -> Callab
683683
inner_type = get_proper_type(inner_type)
684684
outer_type: CallableType | None = None
685685
if inner_type is not None and not isinstance(inner_type, AnyType):
686+
if isinstance(inner_type, TypeVarLikeType):
687+
inner_type = get_proper_type(inner_type.upper_bound)
686688
if isinstance(inner_type, TypeType):
687689
if isinstance(inner_type.item, Instance):
688690
inner_type = expand_type_by_instance(

mypy/fastparse.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,12 +331,10 @@ def parse_type_string(
331331
"""
332332
try:
333333
_, node = parse_type_comment(f"({expr_string})", line=line, column=column, errors=None)
334-
if isinstance(node, UnboundType) and node.original_str_expr is None:
334+
if isinstance(node, (UnboundType, UnionType)) and node.original_str_expr is None:
335335
node.original_str_expr = expr_string
336336
node.original_str_fallback = expr_fallback_name
337337
return node
338-
elif isinstance(node, UnionType):
339-
return node
340338
else:
341339
return RawExpressionType(expr_string, expr_fallback_name, line, column)
342340
except (SyntaxError, ValueError):

mypy/stubgen.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import mypy.parse
5555
import mypy.traverser
5656
import mypy.util
57+
import mypy.version
5758
from mypy.build import build
5859
from mypy.errors import CompileError, Errors
5960
from mypy.find_sources import InvalidSourceList, create_source_list
@@ -304,9 +305,26 @@ def visit_name_expr(self, node: NameExpr) -> str:
304305
def visit_member_expr(self, o: MemberExpr) -> str:
305306
return self._visit_ref_expr(o)
306307

307-
def visit_str_expr(self, node: StrExpr) -> str:
308+
def _visit_literal_node(
309+
self, node: StrExpr | BytesExpr | IntExpr | FloatExpr | ComplexExpr
310+
) -> str:
308311
return repr(node.value)
309312

313+
def visit_str_expr(self, node: StrExpr) -> str:
314+
return self._visit_literal_node(node)
315+
316+
def visit_bytes_expr(self, node: BytesExpr) -> str:
317+
return f"b{self._visit_literal_node(node)}"
318+
319+
def visit_int_expr(self, node: IntExpr) -> str:
320+
return self._visit_literal_node(node)
321+
322+
def visit_float_expr(self, node: FloatExpr) -> str:
323+
return self._visit_literal_node(node)
324+
325+
def visit_complex_expr(self, node: ComplexExpr) -> str:
326+
return self._visit_literal_node(node)
327+
310328
def visit_index_expr(self, node: IndexExpr) -> str:
311329
base_fullname = self.stubgen.get_fullname(node.base)
312330
if base_fullname == "typing.Union":
@@ -804,7 +822,8 @@ def get_base_types(self, cdef: ClassDef) -> list[str]:
804822
for name, value in cdef.keywords.items():
805823
if name == "metaclass":
806824
continue # handled separately
807-
base_types.append(f"{name}={value.accept(p)}")
825+
processed_value = value.accept(p) or "..." # at least, don't crash
826+
base_types.append(f"{name}={processed_value}")
808827
return base_types
809828

810829
def get_class_decorators(self, cdef: ClassDef) -> list[str]:
@@ -1847,6 +1866,9 @@ def parse_options(args: list[str]) -> Options:
18471866
dest="files",
18481867
help="generate stubs for given files or directories",
18491868
)
1869+
parser.add_argument(
1870+
"--version", action="version", version="%(prog)s " + mypy.version.__version__
1871+
)
18501872

18511873
ns = parser.parse_args(args)
18521874

mypy/stubtest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,9 @@ def _verify_readonly_property(stub: nodes.Decorator, runtime: Any) -> Iterator[s
12241224
if isinstance(runtime, property):
12251225
yield from _verify_final_method(stub.func, runtime.fget, MISSING)
12261226
return
1227+
if isinstance(runtime, functools.cached_property):
1228+
yield from _verify_final_method(stub.func, runtime.func, MISSING)
1229+
return
12271230
if inspect.isdatadescriptor(runtime):
12281231
# It's enough like a property...
12291232
return

mypy/test/teststubtest.py

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,9 @@ def __invert__(self: _T) -> _T: pass
144144
"""
145145

146146

147-
def run_stubtest(
147+
def run_stubtest_with_stderr(
148148
stub: str, runtime: str, options: list[str], config_file: str | None = None
149-
) -> str:
149+
) -> tuple[str, str]:
150150
with use_tmp_dir(TEST_MODULE_NAME) as tmp_dir:
151151
with open("builtins.pyi", "w") as f:
152152
f.write(stubtest_builtins_stub)
@@ -163,13 +163,26 @@ def run_stubtest(
163163
f.write(config_file)
164164
options = options + ["--mypy-config-file", f"{TEST_MODULE_NAME}_config.ini"]
165165
output = io.StringIO()
166-
with contextlib.redirect_stdout(output):
166+
outerr = io.StringIO()
167+
with contextlib.redirect_stdout(output), contextlib.redirect_stderr(outerr):
167168
test_stubs(parse_options([TEST_MODULE_NAME] + options), use_builtins_fixtures=True)
168-
return remove_color_code(
169-
output.getvalue()
170-
# remove cwd as it's not available from outside
171-
.replace(os.path.realpath(tmp_dir) + os.sep, "").replace(tmp_dir + os.sep, "")
172-
)
169+
filtered_output = remove_color_code(
170+
output.getvalue()
171+
# remove cwd as it's not available from outside
172+
.replace(os.path.realpath(tmp_dir) + os.sep, "").replace(tmp_dir + os.sep, "")
173+
)
174+
filtered_outerr = remove_color_code(
175+
outerr.getvalue()
176+
# remove cwd as it's not available from outside
177+
.replace(os.path.realpath(tmp_dir) + os.sep, "").replace(tmp_dir + os.sep, "")
178+
)
179+
return filtered_output, filtered_outerr
180+
181+
182+
def run_stubtest(
183+
stub: str, runtime: str, options: list[str], config_file: str | None = None
184+
) -> str:
185+
return run_stubtest_with_stderr(stub, runtime, options, config_file)[0]
173186

174187

175188
class Case:
@@ -893,6 +906,106 @@ class FineAndDandy:
893906
error=None,
894907
)
895908

909+
@collect_cases
910+
def test_cached_property(self) -> Iterator[Case]:
911+
yield Case(
912+
stub="""
913+
from functools import cached_property
914+
class Good:
915+
@cached_property
916+
def read_only_attr(self) -> int: ...
917+
@cached_property
918+
def read_only_attr2(self) -> int: ...
919+
""",
920+
runtime="""
921+
import functools as ft
922+
from functools import cached_property
923+
class Good:
924+
@cached_property
925+
def read_only_attr(self): return 1
926+
@ft.cached_property
927+
def read_only_attr2(self): return 1
928+
""",
929+
error=None,
930+
)
931+
yield Case(
932+
stub="""
933+
from functools import cached_property
934+
class Bad:
935+
@cached_property
936+
def f(self) -> int: ...
937+
""",
938+
runtime="""
939+
class Bad:
940+
def f(self) -> int: return 1
941+
""",
942+
error="Bad.f",
943+
)
944+
yield Case(
945+
stub="""
946+
from functools import cached_property
947+
class GoodCachedAttr:
948+
@cached_property
949+
def f(self) -> int: ...
950+
""",
951+
runtime="""
952+
class GoodCachedAttr:
953+
f = 1
954+
""",
955+
error=None,
956+
)
957+
yield Case(
958+
stub="""
959+
from functools import cached_property
960+
class BadCachedAttr:
961+
@cached_property
962+
def f(self) -> str: ...
963+
""",
964+
runtime="""
965+
class BadCachedAttr:
966+
f = 1
967+
""",
968+
error="BadCachedAttr.f",
969+
)
970+
yield Case(
971+
stub="""
972+
from functools import cached_property
973+
from typing import final
974+
class FinalGood:
975+
@cached_property
976+
@final
977+
def attr(self) -> int: ...
978+
""",
979+
runtime="""
980+
from functools import cached_property
981+
from typing import final
982+
class FinalGood:
983+
@cached_property
984+
@final
985+
def attr(self):
986+
return 1
987+
""",
988+
error=None,
989+
)
990+
yield Case(
991+
stub="""
992+
from functools import cached_property
993+
class FinalBad:
994+
@cached_property
995+
def attr(self) -> int: ...
996+
""",
997+
runtime="""
998+
from functools import cached_property
999+
from typing_extensions import final
1000+
class FinalBad:
1001+
@cached_property
1002+
@final
1003+
def attr(self):
1004+
return 1
1005+
""",
1006+
error="FinalBad.attr",
1007+
)
1008+
8961009
@collect_cases
8971010
def test_var(self) -> Iterator[Case]:
8981011
yield Case(stub="x1: int", runtime="x1 = 5", error=None)
@@ -2490,6 +2603,19 @@ def test_config_file_error_codes(self) -> None:
24902603
output = run_stubtest(stub=stub, runtime=runtime, options=[], config_file=config_file)
24912604
assert output == "Success: no issues found in 1 module\n"
24922605

2606+
def test_config_file_error_codes_invalid(self) -> None:
2607+
runtime = "temp = 5\n"
2608+
stub = "temp: int\n"
2609+
config_file = "[mypy]\ndisable_error_code = not-a-valid-name\n"
2610+
output, outerr = run_stubtest_with_stderr(
2611+
stub=stub, runtime=runtime, options=[], config_file=config_file
2612+
)
2613+
assert output == "Success: no issues found in 1 module\n"
2614+
assert outerr == (
2615+
"test_module_config.ini: [mypy]: disable_error_code: "
2616+
"Invalid error code(s): not-a-valid-name\n"
2617+
)
2618+
24932619
def test_config_file_wrong_incomplete_feature(self) -> None:
24942620
runtime = "x = 1\n"
24952621
stub = "x: int\n"

mypy/typeanal.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1615,7 +1615,11 @@ def analyze_literal_type(self, t: UnboundType) -> Type:
16151615

16161616
def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> list[Type] | None:
16171617
# This UnboundType was originally defined as a string.
1618-
if isinstance(arg, UnboundType) and arg.original_str_expr is not None:
1618+
if (
1619+
isinstance(arg, ProperType)
1620+
and isinstance(arg, (UnboundType, UnionType))
1621+
and arg.original_str_expr is not None
1622+
):
16191623
assert arg.original_str_fallback is not None
16201624
return [
16211625
LiteralType(

mypy/types.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,7 @@ class UnboundType(ProperType):
914914

915915
def __init__(
916916
self,
917-
name: str | None,
917+
name: str,
918918
args: Sequence[Type] | None = None,
919919
line: int = -1,
920920
column: int = -1,
@@ -926,7 +926,6 @@ def __init__(
926926
super().__init__(line, column)
927927
if not args:
928928
args = []
929-
assert name is not None
930929
self.name = name
931930
self.args = tuple(args)
932931
# Should this type be wrapped in an Optional?
@@ -2867,7 +2866,13 @@ def is_singleton_type(self) -> bool:
28672866
class UnionType(ProperType):
28682867
"""The union type Union[T1, ..., Tn] (at least one type argument)."""
28692868

2870-
__slots__ = ("items", "is_evaluated", "uses_pep604_syntax")
2869+
__slots__ = (
2870+
"items",
2871+
"is_evaluated",
2872+
"uses_pep604_syntax",
2873+
"original_str_expr",
2874+
"original_str_fallback",
2875+
)
28712876

28722877
def __init__(
28732878
self,
@@ -2886,6 +2891,11 @@ def __init__(
28862891
self.is_evaluated = is_evaluated
28872892
# uses_pep604_syntax is True if Union uses OR syntax (X | Y)
28882893
self.uses_pep604_syntax = uses_pep604_syntax
2894+
# The meaning of these two is the same as for UnboundType. A UnionType can be
2895+
# return by type parser from a string "A|B", and we need to be able to fall back
2896+
# to plain string, when such a string appears inside a Literal[...].
2897+
self.original_str_expr: str | None = None
2898+
self.original_str_fallback: str | None = None
28892899

28902900
def can_be_true_default(self) -> bool:
28912901
return any(item.can_be_true for item in self.items)

mypyc/lib-rt/dict_ops.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,11 @@ PyObject *CPyDict_SetDefault(PyObject *dict, PyObject *key, PyObject *value) {
7878
return ret;
7979
}
8080
_Py_IDENTIFIER(setdefault);
81-
return _PyObject_CallMethodIdObjArgs(dict, &PyId_setdefault, key, value, NULL);
81+
PyObject *name = _PyUnicode_FromId(&PyId_setdefault); /* borrowed */
82+
if (name == NULL) {
83+
return NULL;
84+
}
85+
return PyObject_CallMethodObjArgs(dict, name, key, value, NULL);
8286
}
8387

8488
PyObject *CPyDict_SetDefaultWithNone(PyObject *dict, PyObject *key) {
@@ -133,7 +137,11 @@ static inline int CPy_ObjectToStatus(PyObject *obj) {
133137

134138
static int CPyDict_UpdateGeneral(PyObject *dict, PyObject *stuff) {
135139
_Py_IDENTIFIER(update);
136-
PyObject *res = _PyObject_CallMethodIdOneArg(dict, &PyId_update, stuff);
140+
PyObject *name = _PyUnicode_FromId(&PyId_update); /* borrowed */
141+
if (name == NULL) {
142+
return -1;
143+
}
144+
PyObject *res = PyObject_CallMethodOneArg(dict, name, stuff);
137145
return CPy_ObjectToStatus(res);
138146
}
139147

mypyc/lib-rt/pythonsupport.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,8 @@ _CPyObject_HasAttrId(PyObject *v, _Py_Identifier *name) {
401401
_PyObject_CallMethodIdObjArgs((self), (name), NULL)
402402
#define _PyObject_CallMethodIdOneArg(self, name, arg) \
403403
_PyObject_CallMethodIdObjArgs((self), (name), (arg), NULL)
404+
#define PyObject_CallMethodOneArg(self, name, arg) \
405+
PyObject_CallMethodObjArgs((self), (name), (arg), NULL)
404406
#endif
405407

406408
#if CPY_3_13_FEATURES

test-data/unit/check-functools.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,3 +541,19 @@ p(1, "no") # E: Argument 2 to "A" has incompatible type "str"; expected "int"
541541

542542
q: partial[A] = partial(A, 1) # OK
543543
[builtins fixtures/tuple.pyi]
544+
545+
[case testFunctoolsPartialTypeVarBound]
546+
from typing import Callable, TypeVar, Type
547+
import functools
548+
549+
T = TypeVar("T", bound=Callable[[str, int], str])
550+
S = TypeVar("S", bound=Type[int])
551+
552+
def foo(f: T) -> T:
553+
g = functools.partial(f, "foo")
554+
return f
555+
556+
def bar(f: S) -> S:
557+
g = functools.partial(f, "foo")
558+
return f
559+
[builtins fixtures/primitives.pyi]

0 commit comments

Comments
 (0)