Skip to content

Commit

Permalink
Merge branch 'master' into fix-tuple-special-cases-docstrings
Browse files Browse the repository at this point in the history
  • Loading branch information
InvincibleRMC committed Jun 29, 2024
2 parents 9f20e36 + 177c8ee commit 74bbda4
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 49 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ This feature was contributed by Shantanu (PR [16756](https://github.com/python/m

Please see [git log](https://github.com/python/typeshed/commits/main?after=7c8e82fe483a40ec4cb0a2505cfdb0f3e7cc81d9+0&branch=main&path=stdlib) for full list of standard library typeshed stub changes.

#### Mypy 1.10.1

- Fix error reporting on cached run after uninstallation of third party library (Shantanu, PR [17420](https://github.com/python/mypy/pull/17420))

#### Acknowledgements
Thanks to all mypy contributors who contributed to this release:
Expand Down
2 changes: 1 addition & 1 deletion docs/source/error_code_list2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ Example:
# Are you missing an await?
asyncio.create_task(f())
You can assign the value to a temporary, otherwise unused to variable to
You can assign the value to a temporary, otherwise unused variable to
silence the error:

.. code-block:: python
Expand Down
13 changes: 2 additions & 11 deletions mypy/erasetype.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
get_proper_type,
get_proper_types,
)
from mypy.typevartuples import erased_vars


def erase_type(typ: Type) -> ProperType:
Expand Down Expand Up @@ -77,17 +78,7 @@ def visit_deleted_type(self, t: DeletedType) -> ProperType:
return t

def visit_instance(self, t: Instance) -> ProperType:
args: list[Type] = []
for tv in t.type.defn.type_vars:
# Valid erasure for *Ts is *tuple[Any, ...], not just Any.
if isinstance(tv, TypeVarTupleType):
args.append(
UnpackType(
tv.tuple_fallback.copy_modified(args=[AnyType(TypeOfAny.special_form)])
)
)
else:
args.append(AnyType(TypeOfAny.special_form))
args = erased_vars(t.type.defn.type_vars, TypeOfAny.special_form)
return Instance(t.type, args, t.line)

def visit_type_var(self, t: TypeVarType) -> ProperType:
Expand Down
3 changes: 0 additions & 3 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3165,9 +3165,6 @@ def add_type_vars(self) -> None:
self.type_var_tuple_prefix = i
self.type_var_tuple_suffix = len(self.defn.type_vars) - i - 1
self.type_vars.append(vd.name)
assert not (
self.has_param_spec_type and self.has_type_var_tuple_type
), "Mixing type var tuples and param specs not supported yet"

@property
def name(self) -> str:
Expand Down
17 changes: 16 additions & 1 deletion mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
deserialize_and_fixup_type,
)
from mypy.server.trigger import make_wildcard_trigger
from mypy.state import state
from mypy.typeops import get_type_vars, make_simplified_union, map_type_from_supertype
from mypy.types import (
AnyType,
Expand Down Expand Up @@ -317,9 +318,23 @@ def attr_class_maker_callback(
See https://www.attrs.org/en/stable/how-does-it-work.html for information on how attrs works.
If this returns False, some required metadata was not ready yet and we need another
If this returns False, some required metadata was not ready yet, and we need another
pass.
"""
with state.strict_optional_set(ctx.api.options.strict_optional):
# This hook is called during semantic analysis, but it uses a bunch of
# type-checking ops, so it needs the strict optional set properly.
return attr_class_maker_callback_impl(
ctx, auto_attribs_default, frozen_default, slots_default
)


def attr_class_maker_callback_impl(
ctx: mypy.plugin.ClassDefContext,
auto_attribs_default: bool | None,
frozen_default: bool,
slots_default: bool,
) -> bool:
info = ctx.cls.info

init = _get_decorator_bool_argument(ctx, "init", True)
Expand Down
26 changes: 20 additions & 6 deletions mypy/semanal_typeargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
get_proper_types,
split_with_prefix_and_suffix,
)
from mypy.typevartuples import erased_vars


class TypeArgumentAnalyzer(MixedTraverserVisitor):
Expand Down Expand Up @@ -89,7 +90,14 @@ def visit_type_alias_type(self, t: TypeAliasType) -> None:
return
self.seen_aliases.add(t)
assert t.alias is not None, f"Unfixed type alias {t.type_ref}"
is_error = self.validate_args(t.alias.name, tuple(t.args), t.alias.alias_tvars, t)
is_error, is_invalid = self.validate_args(
t.alias.name, tuple(t.args), t.alias.alias_tvars, t
)
if is_invalid:
# If there is an arity error (e.g. non-Parameters used for ParamSpec etc.),
# then it is safer to erase the arguments completely, to avoid crashes later.
# TODO: can we move this logic to typeanal.py?
t.args = erased_vars(t.alias.alias_tvars, TypeOfAny.from_error)
if not is_error:
# If there was already an error for the alias itself, there is no point in checking
# the expansion, most likely it will result in the same kind of error.
Expand All @@ -113,7 +121,9 @@ def visit_instance(self, t: Instance) -> None:
info = t.type
if isinstance(info, FakeInfo):
return # https://github.com/python/mypy/issues/11079
self.validate_args(info.name, t.args, info.defn.type_vars, t)
_, is_invalid = self.validate_args(info.name, t.args, info.defn.type_vars, t)
if is_invalid:
t.args = tuple(erased_vars(info.defn.type_vars, TypeOfAny.from_error))
if t.type.fullname == "builtins.tuple" and len(t.args) == 1:
# Normalize Tuple[*Tuple[X, ...], ...] -> Tuple[X, ...]
arg = t.args[0]
Expand All @@ -125,7 +135,7 @@ def visit_instance(self, t: Instance) -> None:

def validate_args(
self, name: str, args: tuple[Type, ...], type_vars: list[TypeVarLikeType], ctx: Context
) -> bool:
) -> tuple[bool, bool]:
if any(isinstance(v, TypeVarTupleType) for v in type_vars):
prefix = next(i for (i, v) in enumerate(type_vars) if isinstance(v, TypeVarTupleType))
tvt = type_vars[prefix]
Expand All @@ -136,10 +146,11 @@ def validate_args(
args = start + (TupleType(list(middle), tvt.tuple_fallback),) + end

is_error = False
is_invalid = False
for (i, arg), tvar in zip(enumerate(args), type_vars):
if isinstance(tvar, TypeVarType):
if isinstance(arg, ParamSpecType):
is_error = True
is_invalid = True
self.fail(
INVALID_PARAM_SPEC_LOCATION.format(format_type(arg, self.options)),
ctx,
Expand All @@ -152,7 +163,7 @@ def validate_args(
)
continue
if isinstance(arg, Parameters):
is_error = True
is_invalid = True
self.fail(
f"Cannot use {format_type(arg, self.options)} for regular type variable,"
" only for ParamSpec",
Expand Down Expand Up @@ -205,13 +216,16 @@ def validate_args(
if not isinstance(
get_proper_type(arg), (ParamSpecType, Parameters, AnyType, UnboundType)
):
is_invalid = True
self.fail(
"Can only replace ParamSpec with a parameter types list or"
f" another ParamSpec, got {format_type(arg, self.options)}",
ctx,
code=codes.VALID_TYPE,
)
return is_error
if is_invalid:
is_error = True
return is_error, is_invalid

def visit_unpack_type(self, typ: UnpackType) -> None:
super().visit_unpack_type(typ)
Expand Down
3 changes: 2 additions & 1 deletion mypy/stubdoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
Sig: _TypeAlias = Tuple[str, str]


_TYPE_RE: Final = re.compile(r"^[a-zA-Z_][\w\[\], ().]*(\.[a-zA-Z_][\w\[\], ]*)*$")

_TYPE_RE: Final = re.compile(r"^[a-zA-Z_][\w\[\], ().\"\']*(\.[a-zA-Z_][\w\[\], ]*)*$")
_ARG_NAME_RE: Final = re.compile(r"\**[A-Za-z_][A-Za-z0-9_]*$")


Expand Down
57 changes: 42 additions & 15 deletions mypy/stubgenc.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import keyword
import os.path
from types import FunctionType, ModuleType
from typing import Any, Mapping
from typing import Any, Callable, Mapping

from mypy.fastparse import parse_type_comment
from mypy.moduleinspect import is_c_module
Expand Down Expand Up @@ -252,6 +252,7 @@ def __init__(
"Iterable",
"Iterator",
"List",
"Literal",
"NamedTuple",
"Optional",
"Tuple",
Expand Down Expand Up @@ -291,6 +292,8 @@ def get_default_function_sig(self, func: object, ctx: FunctionContext) -> Functi
varargs = argspec.varargs
kwargs = argspec.varkw
annotations = argspec.annotations
kwonlyargs = argspec.kwonlyargs
kwonlydefaults = argspec.kwonlydefaults

def get_annotation(key: str) -> str | None:
if key not in annotations:
Expand All @@ -303,27 +306,51 @@ def get_annotation(key: str) -> str | None:
return argtype

arglist: list[ArgSig] = []

# Add the arguments to the signature
for i, arg in enumerate(args):
# Check if the argument has a default value
if defaults and i >= len(args) - len(defaults):
default_value = defaults[i - (len(args) - len(defaults))]
if arg in annotations:
argtype = annotations[arg]
def add_args(
args: list[str], get_default_value: Callable[[int, str], object | None]
) -> None:
for i, arg in enumerate(args):
# Check if the argument has a default value
default_value = get_default_value(i, arg)
if default_value is not None:
if arg in annotations:
argtype = annotations[arg]
else:
argtype = self.get_type_annotation(default_value)
if argtype == "None":
# None is not a useful annotation, but we can infer that the arg
# is optional
incomplete = self.add_name("_typeshed.Incomplete")
argtype = f"{incomplete} | None"

arglist.append(ArgSig(arg, argtype, default=True))
else:
argtype = self.get_type_annotation(default_value)
if argtype == "None":
# None is not a useful annotation, but we can infer that the arg
# is optional
incomplete = self.add_name("_typeshed.Incomplete")
argtype = f"{incomplete} | None"
arglist.append(ArgSig(arg, argtype, default=True))
arglist.append(ArgSig(arg, get_annotation(arg), default=False))

def get_pos_default(i: int, _arg: str) -> Any | None:
if defaults and i >= len(args) - len(defaults):
return defaults[i - (len(args) - len(defaults))]
else:
arglist.append(ArgSig(arg, get_annotation(arg), default=False))
return None

add_args(args, get_pos_default)

# Add *args if present
if varargs:
arglist.append(ArgSig(f"*{varargs}", get_annotation(varargs)))
# if we have keyword only args, then wee need to add "*"
elif kwonlyargs:
arglist.append(ArgSig("*"))

def get_kw_default(_i: int, arg: str) -> Any | None:
if kwonlydefaults:
return kwonlydefaults.get(arg)
else:
return None

add_args(kwonlyargs, get_kw_default)

# Add **kwargs if present
if kwargs:
Expand Down
40 changes: 40 additions & 0 deletions mypy/test/teststubgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,35 @@ class TestClassVariableCls:
assert_equal(gen.get_imports().splitlines(), ["from typing import ClassVar"])
assert_equal(output, ["class C:", " x: ClassVar[int] = ..."])

def test_non_c_generate_signature_with_kw_only_args(self) -> None:
class TestClass:
def test(
self, arg0: str, *, keyword_only: str, keyword_only_with_default: int = 7
) -> None:
pass

output: list[str] = []
mod = ModuleType(TestClass.__module__, "")
gen = InspectionStubGenerator(mod.__name__, known_modules=[mod.__name__], module=mod)
gen.is_c_module = False
gen.generate_function_stub(
"test",
TestClass.test,
output=output,
class_info=ClassInfo(
self_var="self",
cls=TestClass,
name="TestClass",
docstring=getattr(TestClass, "__doc__", None),
),
)
assert_equal(
output,
[
"def test(self, arg0: str, *, keyword_only: str, keyword_only_with_default: int = ...) -> None: ..."
],
)

def test_generate_c_type_inheritance(self) -> None:
class TestClass(KeyError):
pass
Expand Down Expand Up @@ -1360,6 +1389,17 @@ def test_is_valid_type(self) -> None:
assert is_valid_type("tuple[int]")
assert is_valid_type("tuple[()]")
assert is_valid_type("tuple[int, ...]")
assert is_valid_type("Literal[26]")
assert is_valid_type("Literal[0x1A]")
assert is_valid_type('Literal["hello world"]')
assert is_valid_type('Literal[b"hello world"]')
assert is_valid_type('Literal[u"hello world"]')
assert is_valid_type("Literal[True]")
assert is_valid_type("Literal[Color.RED]")
assert is_valid_type("Literal[None]")
assert is_valid_type(
'Literal[26, 0x1A, "hello world", b"hello world", u"hello world", True, Color.RED, None]'
)
assert not is_valid_type("foo-bar")
assert not is_valid_type("x->y")
assert not is_valid_type("True")
Expand Down
13 changes: 2 additions & 11 deletions mypy/typevars.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from mypy.erasetype import erase_typevars
from mypy.nodes import TypeInfo
from mypy.types import (
AnyType,
Instance,
ParamSpecType,
ProperType,
Expand All @@ -15,6 +14,7 @@
TypeVarType,
UnpackType,
)
from mypy.typevartuples import erased_vars


def fill_typevars(typ: TypeInfo) -> Instance | TupleType:
Expand Down Expand Up @@ -64,16 +64,7 @@ def fill_typevars(typ: TypeInfo) -> Instance | TupleType:

def fill_typevars_with_any(typ: TypeInfo) -> Instance | TupleType:
"""Apply a correct number of Any's as type arguments to a type."""
args: list[Type] = []
for tv in typ.defn.type_vars:
# Valid erasure for *Ts is *tuple[Any, ...], not just Any.
if isinstance(tv, TypeVarTupleType):
args.append(
UnpackType(tv.tuple_fallback.copy_modified(args=[AnyType(TypeOfAny.special_form)]))
)
else:
args.append(AnyType(TypeOfAny.special_form))
inst = Instance(typ, args)
inst = Instance(typ, erased_vars(typ.defn.type_vars, TypeOfAny.special_form))
if typ.tuple_type is None:
return inst
erased_tuple_type = erase_typevars(typ.tuple_type, {tv.id for tv in typ.defn.type_vars})
Expand Down
14 changes: 14 additions & 0 deletions mypy/typevartuples.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
from typing import Sequence

from mypy.types import (
AnyType,
Instance,
ProperType,
Type,
TypeVarLikeType,
TypeVarTupleType,
UnpackType,
get_proper_type,
split_with_prefix_and_suffix,
Expand All @@ -30,3 +33,14 @@ def extract_unpack(types: Sequence[Type]) -> ProperType | None:
if isinstance(types[0], UnpackType):
return get_proper_type(types[0].type)
return None


def erased_vars(type_vars: Sequence[TypeVarLikeType], type_of_any: int) -> list[Type]:
args: list[Type] = []
for tv in type_vars:
# Valid erasure for *Ts is *tuple[Any, ...], not just Any.
if isinstance(tv, TypeVarTupleType):
args.append(UnpackType(tv.tuple_fallback.copy_modified(args=[AnyType(type_of_any)])))
else:
args.append(AnyType(type_of_any))
return args
Loading

0 comments on commit 74bbda4

Please sign in to comment.