Skip to content

Commit

Permalink
Merge branch 'master' into fix-enum-truthyness
Browse files Browse the repository at this point in the history
  • Loading branch information
Daverball committed Aug 18, 2024
2 parents 37dbd40 + fe15ee6 commit a05e4a7
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 19 deletions.
2 changes: 2 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,8 @@ def extract_callable_type(self, inner_type: Type | None, ctx: Context) -> Callab
inner_type = get_proper_type(inner_type)
outer_type: CallableType | None = None
if inner_type is not None and not isinstance(inner_type, AnyType):
if isinstance(inner_type, TypeVarLikeType):
inner_type = get_proper_type(inner_type.upper_bound)
if isinstance(inner_type, TypeType):
if isinstance(inner_type.item, Instance):
inner_type = expand_type_by_instance(
Expand Down
4 changes: 1 addition & 3 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,10 @@ def parse_type_string(
"""
try:
_, node = parse_type_comment(f"({expr_string})", line=line, column=column, errors=None)
if isinstance(node, UnboundType) and node.original_str_expr is None:
if isinstance(node, (UnboundType, UnionType)) and node.original_str_expr is None:
node.original_str_expr = expr_string
node.original_str_fallback = expr_fallback_name
return node
elif isinstance(node, UnionType):
return node
else:
return RawExpressionType(expr_string, expr_fallback_name, line, column)
except (SyntaxError, ValueError):
Expand Down
26 changes: 24 additions & 2 deletions mypy/stubgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import mypy.parse
import mypy.traverser
import mypy.util
import mypy.version
from mypy.build import build
from mypy.errors import CompileError, Errors
from mypy.find_sources import InvalidSourceList, create_source_list
Expand Down Expand Up @@ -304,9 +305,26 @@ def visit_name_expr(self, node: NameExpr) -> str:
def visit_member_expr(self, o: MemberExpr) -> str:
return self._visit_ref_expr(o)

def visit_str_expr(self, node: StrExpr) -> str:
def _visit_literal_node(
self, node: StrExpr | BytesExpr | IntExpr | FloatExpr | ComplexExpr
) -> str:
return repr(node.value)

def visit_str_expr(self, node: StrExpr) -> str:
return self._visit_literal_node(node)

def visit_bytes_expr(self, node: BytesExpr) -> str:
return f"b{self._visit_literal_node(node)}"

def visit_int_expr(self, node: IntExpr) -> str:
return self._visit_literal_node(node)

def visit_float_expr(self, node: FloatExpr) -> str:
return self._visit_literal_node(node)

def visit_complex_expr(self, node: ComplexExpr) -> str:
return self._visit_literal_node(node)

def visit_index_expr(self, node: IndexExpr) -> str:
base_fullname = self.stubgen.get_fullname(node.base)
if base_fullname == "typing.Union":
Expand Down Expand Up @@ -804,7 +822,8 @@ def get_base_types(self, cdef: ClassDef) -> list[str]:
for name, value in cdef.keywords.items():
if name == "metaclass":
continue # handled separately
base_types.append(f"{name}={value.accept(p)}")
processed_value = value.accept(p) or "..." # at least, don't crash
base_types.append(f"{name}={processed_value}")
return base_types

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

ns = parser.parse_args(args)

Expand Down
3 changes: 3 additions & 0 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,9 @@ def _verify_readonly_property(stub: nodes.Decorator, runtime: Any) -> Iterator[s
if isinstance(runtime, property):
yield from _verify_final_method(stub.func, runtime.fget, MISSING)
return
if isinstance(runtime, functools.cached_property):
yield from _verify_final_method(stub.func, runtime.func, MISSING)
return
if inspect.isdatadescriptor(runtime):
# It's enough like a property...
return
Expand Down
142 changes: 134 additions & 8 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ def __invert__(self: _T) -> _T: pass
"""


def run_stubtest(
def run_stubtest_with_stderr(
stub: str, runtime: str, options: list[str], config_file: str | None = None
) -> str:
) -> tuple[str, str]:
with use_tmp_dir(TEST_MODULE_NAME) as tmp_dir:
with open("builtins.pyi", "w") as f:
f.write(stubtest_builtins_stub)
Expand All @@ -163,13 +163,26 @@ def run_stubtest(
f.write(config_file)
options = options + ["--mypy-config-file", f"{TEST_MODULE_NAME}_config.ini"]
output = io.StringIO()
with contextlib.redirect_stdout(output):
outerr = io.StringIO()
with contextlib.redirect_stdout(output), contextlib.redirect_stderr(outerr):
test_stubs(parse_options([TEST_MODULE_NAME] + options), use_builtins_fixtures=True)
return remove_color_code(
output.getvalue()
# remove cwd as it's not available from outside
.replace(os.path.realpath(tmp_dir) + os.sep, "").replace(tmp_dir + os.sep, "")
)
filtered_output = remove_color_code(
output.getvalue()
# remove cwd as it's not available from outside
.replace(os.path.realpath(tmp_dir) + os.sep, "").replace(tmp_dir + os.sep, "")
)
filtered_outerr = remove_color_code(
outerr.getvalue()
# remove cwd as it's not available from outside
.replace(os.path.realpath(tmp_dir) + os.sep, "").replace(tmp_dir + os.sep, "")
)
return filtered_output, filtered_outerr


def run_stubtest(
stub: str, runtime: str, options: list[str], config_file: str | None = None
) -> str:
return run_stubtest_with_stderr(stub, runtime, options, config_file)[0]


class Case:
Expand Down Expand Up @@ -893,6 +906,106 @@ class FineAndDandy:
error=None,
)

@collect_cases
def test_cached_property(self) -> Iterator[Case]:
yield Case(
stub="""
from functools import cached_property
class Good:
@cached_property
def read_only_attr(self) -> int: ...
@cached_property
def read_only_attr2(self) -> int: ...
""",
runtime="""
import functools as ft
from functools import cached_property
class Good:
@cached_property
def read_only_attr(self): return 1
@ft.cached_property
def read_only_attr2(self): return 1
""",
error=None,
)
yield Case(
stub="""
from functools import cached_property
class Bad:
@cached_property
def f(self) -> int: ...
""",
runtime="""
class Bad:
def f(self) -> int: return 1
""",
error="Bad.f",
)
yield Case(
stub="""
from functools import cached_property
class GoodCachedAttr:
@cached_property
def f(self) -> int: ...
""",
runtime="""
class GoodCachedAttr:
f = 1
""",
error=None,
)
yield Case(
stub="""
from functools import cached_property
class BadCachedAttr:
@cached_property
def f(self) -> str: ...
""",
runtime="""
class BadCachedAttr:
f = 1
""",
error="BadCachedAttr.f",
)
yield Case(
stub="""
from functools import cached_property
from typing import final
class FinalGood:
@cached_property
@final
def attr(self) -> int: ...
""",
runtime="""
from functools import cached_property
from typing import final
class FinalGood:
@cached_property
@final
def attr(self):
return 1
""",
error=None,
)
yield Case(
stub="""
from functools import cached_property
class FinalBad:
@cached_property
def attr(self) -> int: ...
""",
runtime="""
from functools import cached_property
from typing_extensions import final
class FinalBad:
@cached_property
@final
def attr(self):
return 1
""",
error="FinalBad.attr",
)

@collect_cases
def test_var(self) -> Iterator[Case]:
yield Case(stub="x1: int", runtime="x1 = 5", error=None)
Expand Down Expand Up @@ -2490,6 +2603,19 @@ def test_config_file_error_codes(self) -> None:
output = run_stubtest(stub=stub, runtime=runtime, options=[], config_file=config_file)
assert output == "Success: no issues found in 1 module\n"

def test_config_file_error_codes_invalid(self) -> None:
runtime = "temp = 5\n"
stub = "temp: int\n"
config_file = "[mypy]\ndisable_error_code = not-a-valid-name\n"
output, outerr = run_stubtest_with_stderr(
stub=stub, runtime=runtime, options=[], config_file=config_file
)
assert output == "Success: no issues found in 1 module\n"
assert outerr == (
"test_module_config.ini: [mypy]: disable_error_code: "
"Invalid error code(s): not-a-valid-name\n"
)

def test_config_file_wrong_incomplete_feature(self) -> None:
runtime = "x = 1\n"
stub = "x: int\n"
Expand Down
6 changes: 5 additions & 1 deletion mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1615,7 +1615,11 @@ def analyze_literal_type(self, t: UnboundType) -> Type:

def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> list[Type] | None:
# This UnboundType was originally defined as a string.
if isinstance(arg, UnboundType) and arg.original_str_expr is not None:
if (
isinstance(arg, ProperType)
and isinstance(arg, (UnboundType, UnionType))
and arg.original_str_expr is not None
):
assert arg.original_str_fallback is not None
return [
LiteralType(
Expand Down
16 changes: 13 additions & 3 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,7 @@ class UnboundType(ProperType):

def __init__(
self,
name: str | None,
name: str,
args: Sequence[Type] | None = None,
line: int = -1,
column: int = -1,
Expand All @@ -926,7 +926,6 @@ def __init__(
super().__init__(line, column)
if not args:
args = []
assert name is not None
self.name = name
self.args = tuple(args)
# Should this type be wrapped in an Optional?
Expand Down Expand Up @@ -2867,7 +2866,13 @@ def is_singleton_type(self) -> bool:
class UnionType(ProperType):
"""The union type Union[T1, ..., Tn] (at least one type argument)."""

__slots__ = ("items", "is_evaluated", "uses_pep604_syntax")
__slots__ = (
"items",
"is_evaluated",
"uses_pep604_syntax",
"original_str_expr",
"original_str_fallback",
)

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

def can_be_true_default(self) -> bool:
return any(item.can_be_true for item in self.items)
Expand Down
12 changes: 10 additions & 2 deletions mypyc/lib-rt/dict_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ PyObject *CPyDict_SetDefault(PyObject *dict, PyObject *key, PyObject *value) {
return ret;
}
_Py_IDENTIFIER(setdefault);
return _PyObject_CallMethodIdObjArgs(dict, &PyId_setdefault, key, value, NULL);
PyObject *name = _PyUnicode_FromId(&PyId_setdefault); /* borrowed */
if (name == NULL) {
return NULL;
}
return PyObject_CallMethodObjArgs(dict, name, key, value, NULL);
}

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

static int CPyDict_UpdateGeneral(PyObject *dict, PyObject *stuff) {
_Py_IDENTIFIER(update);
PyObject *res = _PyObject_CallMethodIdOneArg(dict, &PyId_update, stuff);
PyObject *name = _PyUnicode_FromId(&PyId_update); /* borrowed */
if (name == NULL) {
return -1;
}
PyObject *res = PyObject_CallMethodOneArg(dict, name, stuff);
return CPy_ObjectToStatus(res);
}

Expand Down
2 changes: 2 additions & 0 deletions mypyc/lib-rt/pythonsupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,8 @@ _CPyObject_HasAttrId(PyObject *v, _Py_Identifier *name) {
_PyObject_CallMethodIdObjArgs((self), (name), NULL)
#define _PyObject_CallMethodIdOneArg(self, name, arg) \
_PyObject_CallMethodIdObjArgs((self), (name), (arg), NULL)
#define PyObject_CallMethodOneArg(self, name, arg) \
PyObject_CallMethodObjArgs((self), (name), (arg), NULL)
#endif

#if CPY_3_13_FEATURES
Expand Down
16 changes: 16 additions & 0 deletions test-data/unit/check-functools.test
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,19 @@ p(1, "no") # E: Argument 2 to "A" has incompatible type "str"; expected "int"

q: partial[A] = partial(A, 1) # OK
[builtins fixtures/tuple.pyi]

[case testFunctoolsPartialTypeVarBound]
from typing import Callable, TypeVar, Type
import functools

T = TypeVar("T", bound=Callable[[str, int], str])
S = TypeVar("S", bound=Type[int])

def foo(f: T) -> T:
g = functools.partial(f, "foo")
return f

def bar(f: S) -> S:
g = functools.partial(f, "foo")
return f
[builtins fixtures/primitives.pyi]
4 changes: 4 additions & 0 deletions test-data/unit/check-literal.test
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ reveal_type(g1) # N: Revealed type is "def (x: Literal['A['])"

def f2(x: 'A B') -> None: pass # E: Invalid type comment or annotation
def g2(x: Literal['A B']) -> None: pass
def h2(x: 'A|int') -> None: pass # E: Name "A" is not defined
def i2(x: Literal['A|B']) -> None: pass
reveal_type(f2) # N: Revealed type is "def (x: Any)"
reveal_type(g2) # N: Revealed type is "def (x: Literal['A B'])"
reveal_type(h2) # N: Revealed type is "def (x: Union[Any, builtins.int])"
reveal_type(i2) # N: Revealed type is "def (x: Literal['A|B'])"
[builtins fixtures/tuple.pyi]
[out]

Expand Down
10 changes: 10 additions & 0 deletions test-data/unit/stubgen.test
Original file line number Diff line number Diff line change
Expand Up @@ -4405,3 +4405,13 @@ Y = int | None
Z = Incomplete
W = int | str | None
R = type[int | str] | None

[case testClassInheritanceWithKeywordsConstants]
class Test(Whatever, a=1, b='b', c=True, d=1.5, e=None, f=1j, g=b'123'): ...
[out]
class Test(Whatever, a=1, b='b', c=True, d=1.5, e=None, f=1j, g=b'123'): ...

[case testClassInheritanceWithKeywordsDynamic]
class Test(Whatever, keyword=SomeName * 2, attr=SomeName.attr): ...
[out]
class Test(Whatever, keyword=SomeName * 2, attr=SomeName.attr): ...

0 comments on commit a05e4a7

Please sign in to comment.