Skip to content

Commit

Permalink
Merge branch 'master' into fix-typos-in-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelg100 authored Jul 5, 2024
2 parents 3eebca0 + ec00fb8 commit d5c2564
Show file tree
Hide file tree
Showing 25 changed files with 407 additions and 127 deletions.
19 changes: 9 additions & 10 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5766,16 +5766,15 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F
context=if_type_fallback,
allow_none_return=allow_none_return,
)

# Only create a union type if the type context is a union, to be mostly
# compatible with older mypy versions where we always did a join.
#
# TODO: Always create a union or at least in more cases?
if isinstance(get_proper_type(self.type_context[-1]), UnionType):
res: Type = make_simplified_union([if_type, full_context_else_type])
else:
res = join.join_types(if_type, else_type)

res: Type = make_simplified_union([if_type, else_type])
if has_uninhabited_component(res) and not isinstance(
get_proper_type(self.type_context[-1]), UnionType
):
# In rare cases with empty collections join may give a better result.
alternative = join.join_types(if_type, else_type)
p_alt = get_proper_type(alternative)
if not isinstance(p_alt, Instance) or p_alt.type.fullname != "builtins.object":
res = alternative
return res

def analyze_cond_branch(
Expand Down
12 changes: 11 additions & 1 deletion mypy/expandtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import Final, Iterable, Mapping, Sequence, TypeVar, cast, overload

from mypy.nodes import ARG_STAR, Var
from mypy.nodes import ARG_STAR, FakeInfo, Var
from mypy.state import state
from mypy.types import (
ANY_STRATEGY,
Expand Down Expand Up @@ -208,6 +208,16 @@ def visit_erased_type(self, t: ErasedType) -> Type:

def visit_instance(self, t: Instance) -> Type:
args = self.expand_types_with_unpack(list(t.args))

if isinstance(t.type, FakeInfo):
# The type checker expands function definitions and bodies
# if they depend on constrained type variables but the body
# might contain a tuple type comment (e.g., # type: (int, float)),
# in which case 't.type' is not yet available.
#
# See: https://github.com/python/mypy/issues/16649
return t.copy_modified(args=args)

if t.type.fullname == "builtins.tuple":
# Normalize Tuple[*Tuple[X, ...], ...] -> Tuple[X, ...]
arg = args[0]
Expand Down
16 changes: 13 additions & 3 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,9 @@ def do_func_def(
else:
self.fail(
ErrorMessage(
"PEP 695 generics are not yet supported", code=codes.VALID_TYPE
"PEP 695 generics are not yet supported. "
"Use --enable-incomplete-feature=NewGenericSyntax for experimental support",
code=codes.VALID_TYPE,
),
n.type_params[0].lineno,
n.type_params[0].col_offset,
Expand Down Expand Up @@ -1145,7 +1147,11 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef:
explicit_type_params = self.translate_type_params(n.type_params)
else:
self.fail(
ErrorMessage("PEP 695 generics are not yet supported", code=codes.VALID_TYPE),
ErrorMessage(
"PEP 695 generics are not yet supported. "
"Use --enable-incomplete-feature=NewGenericSyntax for experimental support",
code=codes.VALID_TYPE,
),
n.type_params[0].lineno,
n.type_params[0].col_offset,
blocker=False,
Expand Down Expand Up @@ -1801,7 +1807,11 @@ def visit_TypeAlias(self, n: ast_TypeAlias) -> TypeAliasStmt | AssignmentStmt:
return self.set_line(node, n)
else:
self.fail(
ErrorMessage("PEP 695 type aliases are not yet supported", code=codes.VALID_TYPE),
ErrorMessage(
"PEP 695 type aliases are not yet supported. "
"Use --enable-incomplete-feature=NewGenericSyntax for experimental support",
code=codes.VALID_TYPE,
),
n.lineno,
n.col_offset,
blocker=False,
Expand Down
2 changes: 1 addition & 1 deletion mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ def add_invertible_flag(
"--no-namespace-packages",
dest="namespace_packages",
default=True,
help="Support namespace packages (PEP 420, __init__.py-less)",
help="Disable support for namespace packages (PEP 420, __init__.py-less)",
group=imports_group,
)
imports_group.add_argument(
Expand Down
15 changes: 15 additions & 0 deletions mypy/plugins/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,9 @@ def transform(self) -> bool:

self._add_dataclass_fields_magic_attribute()
self._add_internal_replace_method(attributes)
if self._api.options.python_version >= (3, 13):
self._add_dunder_replace(attributes)

if "__post_init__" in info.names:
self._add_internal_post_init_method(attributes)

Expand All @@ -395,6 +398,18 @@ def transform(self) -> bool:

return True

def _add_dunder_replace(self, attributes: list[DataclassAttribute]) -> None:
"""Add a `__replace__` method to the class, which is used to replace attributes in the `copy` module."""
args = [attr.to_argument(self._cls.info, of="replace") for attr in attributes]
type_vars = [tv for tv in self._cls.type_vars]
add_method_to_class(
self._api,
self._cls,
"__replace__",
args=args,
return_type=Instance(self._cls.info, type_vars),
)

def _add_internal_replace_method(self, attributes: list[DataclassAttribute]) -> None:
"""
Stashes the signature of 'dataclasses.replace(...)' for this specific dataclass
Expand Down
31 changes: 22 additions & 9 deletions mypy/plugins/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,14 @@ def partial_new_callback(ctx: mypy.plugin.FunctionContext) -> Type:
partial_kinds.append(fn_type.arg_kinds[i])
partial_types.append(arg_type)
partial_names.append(fn_type.arg_names[i])
elif actuals:
if any(actual_arg_kinds[j] == ArgKind.ARG_POS for j in actuals):
else:
assert actuals
if any(actual_arg_kinds[j] in (ArgKind.ARG_POS, ArgKind.ARG_STAR) for j in actuals):
# Don't add params for arguments passed positionally
continue
# Add defaulted params for arguments passed via keyword
kind = actual_arg_kinds[actuals[0]]
if kind == ArgKind.ARG_NAMED:
if kind == ArgKind.ARG_NAMED or kind == ArgKind.ARG_STAR2:
kind = ArgKind.ARG_NAMED_OPT
partial_kinds.append(kind)
partial_types.append(arg_type)
Expand Down Expand Up @@ -286,15 +289,25 @@ def partial_call_callback(ctx: mypy.plugin.MethodContext) -> Type:
if len(ctx.arg_types) != 2: # *args, **kwargs
return ctx.default_return_type

args = [a for param in ctx.args for a in param]
arg_kinds = [a for param in ctx.arg_kinds for a in param]
arg_names = [a for param in ctx.arg_names for a in param]
# See comments for similar actual to formal code above
actual_args = []
actual_arg_kinds = []
actual_arg_names = []
seen_args = set()
for i, param in enumerate(ctx.args):
for j, a in enumerate(param):
if a in seen_args:
continue
seen_args.add(a)
actual_args.append(a)
actual_arg_kinds.append(ctx.arg_kinds[i][j])
actual_arg_names.append(ctx.arg_names[i][j])

result = ctx.api.expr_checker.check_call(
callee=partial_type,
args=args,
arg_kinds=arg_kinds,
arg_names=arg_names,
args=actual_args,
arg_kinds=actual_arg_kinds,
arg_names=actual_arg_names,
context=ctx.context,
)
return result[0]
11 changes: 6 additions & 5 deletions mypy/semanal_classprop.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,12 @@ def check_protocol_status(info: TypeInfo, errors: Errors) -> None:
if info.is_protocol:
for type in info.bases:
if not type.type.is_protocol and type.type.fullname != "builtins.object":

def report(message: str, severity: str) -> None:
errors.report(info.line, info.column, message, severity=severity)

report("All bases of a protocol must be protocols", "error")
errors.report(
info.line,
info.column,
"All bases of a protocol must be protocols",
severity="error",
)


def calculate_class_vars(info: TypeInfo) -> None:
Expand Down
4 changes: 3 additions & 1 deletion mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
TYPED_NAMEDTUPLE_NAMES,
AnyType,
CallableType,
Instance,
LiteralType,
TupleType,
Type,
Expand Down Expand Up @@ -631,9 +632,10 @@ def add_method(
args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED_OPT) for var in vars],
)
if self.options.python_version >= (3, 13):
type_vars = [tv for tv in info.defn.type_vars]
add_method(
"__replace__",
ret=None,
ret=Instance(info, type_vars),
args=[Argument(var, var.type, EllipsisExpr(), ARG_NAMED_OPT) for var in vars],
)

Expand Down
1 change: 1 addition & 0 deletions mypy/type_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ def visit_instance(self, t: Instance) -> Type:
line=t.line,
column=t.column,
last_known_value=last_known_value,
extra_attrs=t.extra_attrs,
)

def visit_type_var(self, t: TypeVarType) -> Type:
Expand Down
3 changes: 1 addition & 2 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1417,8 +1417,7 @@ def __init__(
self._hash = -1

# Additional attributes defined per instance of this type. For example modules
# have different attributes per instance of types.ModuleType. This is intended
# to be "short-lived", we don't serialize it, and even don't store as variable type.
# have different attributes per instance of types.ModuleType.
self.extra_attrs = extra_attrs

def accept(self, visitor: TypeVisitor[T]) -> T:
Expand Down
2 changes: 1 addition & 1 deletion mypy/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# - Release versions have the form "1.2.3".
# - Dev versions have the form "1.2.3+dev" (PLUS sign to conform to PEP 440).
# - Before 1.0 we had the form "0.NNN".
__version__ = "1.11.0+dev"
__version__ = "1.12.0+dev"
base_version = __version__

mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
Expand Down
4 changes: 3 additions & 1 deletion mypyc/test-data/irbuild-any.test
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ def f4(a, n, b):
a :: object
n :: int
b :: bool
r0, r1, r2, r3 :: object
r0 :: union[object, int]
r1, r2 :: object
r3 :: union[int, object]
r4 :: int
L0:
if b goto L1 else goto L2 :: bool
Expand Down
34 changes: 34 additions & 0 deletions test-data/unit/check-dataclasses.test
Original file line number Diff line number Diff line change
Expand Up @@ -2489,3 +2489,37 @@ class Base:
class Child(Base):
y: int
[builtins fixtures/dataclasses.pyi]

[case testDunderReplacePresent]
# flags: --python-version 3.13
from dataclasses import dataclass

@dataclass
class Coords:
x: int
y: int


replaced = Coords(2, 4).__replace__(x=2, y=5)
reveal_type(replaced) # N: Revealed type is "__main__.Coords"

replaced = Coords(2, 4).__replace__(x=2)
reveal_type(replaced) # N: Revealed type is "__main__.Coords"

Coords(2, 4).__replace__(x="asdf") # E: Argument "x" to "__replace__" of "Coords" has incompatible type "str"; expected "int"
Coords(2, 4).__replace__(23) # E: Too many positional arguments for "__replace__" of "Coords"
Coords(2, 4).__replace__(23, 25) # E: Too many positional arguments for "__replace__" of "Coords"
Coords(2, 4).__replace__(x=23, y=25, z=42) # E: Unexpected keyword argument "z" for "__replace__" of "Coords"

from typing import Generic, TypeVar
T = TypeVar('T')

@dataclass
class Gen(Generic[T]):
x: T

replaced_2 = Gen(2).__replace__(x=2)
reveal_type(replaced_2) # N: Revealed type is "__main__.Gen[builtins.int]"
Gen(2).__replace__(x="not an int") # E: Argument "x" to "__replace__" of "Gen" has incompatible type "str"; expected "int"

[builtins fixtures/tuple.pyi]
2 changes: 1 addition & 1 deletion test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ a: D = {'x': ''} # E: Incompatible types (expression has type "str", TypedDict
b: D = {'y': ''} # E: Missing key "x" for TypedDict "D" [typeddict-item] \
# E: Extra key "y" for TypedDict "D" [typeddict-unknown-key]
c = D(x=0) if int() else E(x=0, y=0)
c = {} # E: Expected TypedDict key "x" but found no keys [typeddict-item]
c = {} # E: Missing key "x" for TypedDict "D" [typeddict-item]
d: D = {'x': '', 'y': 1} # E: Extra key "y" for TypedDict "D" [typeddict-unknown-key] \
# E: Incompatible types (expression has type "str", TypedDict item "x" has type "int") [typeddict-item]

Expand Down
17 changes: 8 additions & 9 deletions test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1470,10 +1470,9 @@ if int():

[case testConditionalExpressionUnion]
from typing import Union
reveal_type(1 if bool() else 2) # N: Revealed type is "builtins.int"
reveal_type(1 if bool() else '') # N: Revealed type is "builtins.object"
x: Union[int, str] = reveal_type(1 if bool() else '') \
# N: Revealed type is "Union[Literal[1]?, Literal['']?]"
reveal_type(1 if bool() else 2) # N: Revealed type is "Union[Literal[1]?, Literal[2]?]"
reveal_type(1 if bool() else '') # N: Revealed type is "Union[Literal[1]?, Literal['']?]"
x: Union[int, str] = reveal_type(1 if bool() else '') # N: Revealed type is "Union[Literal[1]?, Literal['']?]"
class A:
pass
class B(A):
Expand All @@ -1487,17 +1486,17 @@ b = B()
c = C()
d = D()
reveal_type(a if bool() else b) # N: Revealed type is "__main__.A"
reveal_type(b if bool() else c) # N: Revealed type is "builtins.object"
reveal_type(c if bool() else b) # N: Revealed type is "builtins.object"
reveal_type(c if bool() else a) # N: Revealed type is "builtins.object"
reveal_type(d if bool() else b) # N: Revealed type is "__main__.A"
reveal_type(b if bool() else c) # N: Revealed type is "Union[__main__.B, __main__.C]"
reveal_type(c if bool() else b) # N: Revealed type is "Union[__main__.C, __main__.B]"
reveal_type(c if bool() else a) # N: Revealed type is "Union[__main__.C, __main__.A]"
reveal_type(d if bool() else b) # N: Revealed type is "Union[__main__.D, __main__.B]"
[builtins fixtures/bool.pyi]

[case testConditionalExpressionUnionWithAny]
from typing import Union, Any
a: Any
x: Union[int, str] = reveal_type(a if int() else 1) # N: Revealed type is "Union[Any, Literal[1]?]"
reveal_type(a if int() else 1) # N: Revealed type is "Any"
reveal_type(a if int() else 1) # N: Revealed type is "Union[Any, Literal[1]?]"

[case testConditionalExpressionStatementNoReturn]
from typing import List, Union
Expand Down
17 changes: 15 additions & 2 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -2250,13 +2250,26 @@ def dec(f: Callable[[A, str], None]) -> Callable[[A, int], None]: pass
[out]

[case testUnknownFunctionNotCallable]
from typing import TypeVar

def f() -> None:
pass
def g(x: int) -> None:
pass
h = f if bool() else g
reveal_type(h) # N: Revealed type is "builtins.function"
h(7) # E: Cannot call function of unknown type
reveal_type(h) # N: Revealed type is "Union[def (), def (x: builtins.int)]"
h(7) # E: Too many arguments for "f"

T = TypeVar("T")
def join(x: T, y: T) -> T: ...

h2 = join(f, g)
reveal_type(h2) # N: Revealed type is "builtins.function"
h2(7) # E: Cannot call function of unknown type

h3 = join(g, f)
reveal_type(h3) # N: Revealed type is "builtins.function"
h3(7) # E: Cannot call function of unknown type
[builtins fixtures/bool.pyi]

[case testFunctionWithNameUnderscore]
Expand Down
Loading

0 comments on commit d5c2564

Please sign in to comment.