Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into bugfix/st-paramsp…
Browse files Browse the repository at this point in the history
…ec-with-functools-partial
  • Loading branch information
sterliakov committed Oct 24, 2024
2 parents 5d46d85 + 3420ef1 commit bbaf9de
Show file tree
Hide file tree
Showing 75 changed files with 1,294 additions and 581 deletions.
1 change: 1 addition & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ concurrency:
jobs:
docs:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
TOXENV: docs
TOX_SKIP_MISSING_INTERPRETERS: False
Expand Down
38 changes: 28 additions & 10 deletions .github/workflows/mypy_primer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
matrix:
shard-index: [0, 1, 2, 3, 4]
fail-fast: false
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
with:
Expand Down Expand Up @@ -69,18 +70,35 @@ jobs:
--output concise \
| tee diff_${{ matrix.shard-index }}.txt
) || [ $? -eq 1 ]
- name: Upload mypy_primer diff
uses: actions/upload-artifact@v3
with:
name: mypy_primer_diffs
path: diff_${{ matrix.shard-index }}.txt
- if: ${{ matrix.shard-index }} == 0
- if: ${{ matrix.shard-index == 0 }}
name: Save PR number
run: |
echo ${{ github.event.pull_request.number }} | tee pr_number.txt
- if: ${{ matrix.shard-index }} == 0
name: Upload PR number
uses: actions/upload-artifact@v3
- if: ${{ matrix.shard-index == 0 }}
name: Upload mypy_primer diff + PR number
uses: actions/upload-artifact@v4
with:
name: mypy_primer_diffs-${{ matrix.shard-index }}
path: |
diff_${{ matrix.shard-index }}.txt
pr_number.txt
- name: Upload mypy_primer diff
uses: actions/upload-artifact@v4
if: ${{ matrix.shard-index != 0 }}
with:
name: mypy_primer_diffs-${{ matrix.shard-index }}
path: diff_${{ matrix.shard-index }}.txt

join_artifacts:
name: Join artifacts
runs-on: ubuntu-latest
needs: [mypy_primer]
permissions:
contents: read
steps:
- name: Merge artifacts
uses: actions/upload-artifact/merge@v4
with:
name: mypy_primer_diffs
path: pr_number.txt
pattern: mypy_primer_diffs-*
delete-merged: true
2 changes: 1 addition & 1 deletion .github/workflows/mypy_primer_comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Download diffs
uses: actions/github-script@v6
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/sync_typeshed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
name: Sync typeshed
if: github.repository == 'python/mypy'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ jobs:
toxenv: lint

name: ${{ matrix.name }}
timeout-minutes: 60
env:
TOX_SKIP_MISSING_INTERPRETERS: False
# Rich (pip) -- Disable color for windows + pytest
Expand Down Expand Up @@ -205,6 +206,7 @@ jobs:
python_32bits:
runs-on: ubuntu-latest
name: Test mypyc suite with 32-bit Python
timeout-minutes: 60
env:
TOX_SKIP_MISSING_INTERPRETERS: False
# Rich (pip)
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test_stubgenc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
# Check stub file generation for a small pybind11 project
# (full text match is required to pass)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:

- uses: actions/checkout@v4
Expand Down
49 changes: 49 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,49 @@

## Next release

## Mypy 1.13

We’ve just uploaded mypy 1.13 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)).
Mypy is a static type checker for Python. You can install it as follows:

python3 -m pip install -U mypy

You can read the full documentation for this release on [Read the Docs](http://mypy.readthedocs.io).

Note that unlike typical releases, Mypy 1.13 does not have any changes to type checking semantics
from 1.12.1.

### Improved performance

Mypy 1.13 contains several performance improvements. Users can expect mypy to be 5-20% faster.
In environments with long search paths (such as environments using many editable installs), mypy
can be significantly faster, e.g. 2.2x faster in the use case targeted by these improvements.

Mypy 1.13 allows use of the `orjson` library for handling the cache instead of the stdlib `json`,
for improved performance. You can ensure the presence of `orjson` using the `faster-cache` extra:

python3 -m pip install -U mypy[faster-cache]

Mypy may depend on `orjson` by default in the future.

These improvements were contributed by Shantanu.

List of changes:
* Significantly speed up file handling error paths (Shantanu, PR [17920](https://github.com/python/mypy/pull/17920))
* Use fast path in modulefinder more often (Shantanu, PR [17950](https://github.com/python/mypy/pull/17950))
* Let mypyc optimise os.path.join (Shantanu, PR [17949](https://github.com/python/mypy/pull/17949))
* Make is_sub_path faster (Shantanu, PR [17962](https://github.com/python/mypy/pull/17962))
* Speed up stubs suggestions (Shantanu, PR [17965](https://github.com/python/mypy/pull/17965))
* Use sha1 for hashing (Shantanu, PR [17953](https://github.com/python/mypy/pull/17953))
* Use orjson instead of json, when available (Shantanu, PR [17955](https://github.com/python/mypy/pull/17955))
* Add faster-cache extra, test in CI (Shantanu, PR [17978](https://github.com/python/mypy/pull/17978))

### Acknowledgements
Thanks to all mypy contributors who contributed to this release:

- Shantanu Jain
- Jukka Lehtosalo

## Mypy 1.12

We’ve just uploaded mypy 1.12 to the Python Package Index ([PyPI](https://pypi.org/project/mypy/)). Mypy is a static type
Expand Down Expand Up @@ -261,6 +304,12 @@ This feature was contributed by Ivan Levkivskyi (PR [17457](https://github.com/p

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

### Mypy 1.12.1
* Fix crash when showing partially analyzed type in error message (Ivan Levkivskyi, PR [17961](https://github.com/python/mypy/pull/17961))
* Fix iteration over union (when self type is involved) (Shantanu, PR [17976](https://github.com/python/mypy/pull/17976))
* Fix type object with type var default in union context (Jukka Lehtosalo, PR [17991](https://github.com/python/mypy/pull/17991))
* Revert change to `os.path` stubs affecting use of `os.PathLike[Any]` (Shantanu, PR [17995](https://github.com/python/mypy/pull/17995))

### Acknowledgements
Thanks to all mypy contributors who contributed to this release:

Expand Down
112 changes: 72 additions & 40 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1072,46 +1072,7 @@ def _visit_func_def(self, defn: FuncDef) -> None:
if defn.original_def:
# Override previous definition.
new_type = self.function_type(defn)
if isinstance(defn.original_def, FuncDef):
# Function definition overrides function definition.
old_type = self.function_type(defn.original_def)
if not is_same_type(new_type, old_type):
self.msg.incompatible_conditional_function_def(defn, old_type, new_type)
else:
# Function definition overrides a variable initialized via assignment or a
# decorated function.
orig_type = defn.original_def.type
if orig_type is None:
# If other branch is unreachable, we don't type check it and so we might
# not have a type for the original definition
return
if isinstance(orig_type, PartialType):
if orig_type.type is None:
# Ah this is a partial type. Give it the type of the function.
orig_def = defn.original_def
if isinstance(orig_def, Decorator):
var = orig_def.var
else:
var = orig_def
partial_types = self.find_partial_types(var)
if partial_types is not None:
var.type = new_type
del partial_types[var]
else:
# Trying to redefine something like partial empty list as function.
self.fail(message_registry.INCOMPATIBLE_REDEFINITION, defn)
else:
name_expr = NameExpr(defn.name)
name_expr.node = defn.original_def
self.binder.assign_type(name_expr, new_type, orig_type)
self.check_subtype(
new_type,
orig_type,
defn,
message_registry.INCOMPATIBLE_REDEFINITION,
"redefinition with type",
"original type",
)
self.check_func_def_override(defn, new_type)

def check_func_item(
self,
Expand Down Expand Up @@ -1147,6 +1108,49 @@ def check_func_item(
if dataclasses_plugin.is_processed_dataclass(defn.info):
dataclasses_plugin.check_post_init(self, defn, defn.info)

def check_func_def_override(self, defn: FuncDef, new_type: FunctionLike) -> None:
assert defn.original_def is not None
if isinstance(defn.original_def, FuncDef):
# Function definition overrides function definition.
old_type = self.function_type(defn.original_def)
if not is_same_type(new_type, old_type):
self.msg.incompatible_conditional_function_def(defn, old_type, new_type)
else:
# Function definition overrides a variable initialized via assignment or a
# decorated function.
orig_type = defn.original_def.type
if orig_type is None:
# If other branch is unreachable, we don't type check it and so we might
# not have a type for the original definition
return
if isinstance(orig_type, PartialType):
if orig_type.type is None:
# Ah this is a partial type. Give it the type of the function.
orig_def = defn.original_def
if isinstance(orig_def, Decorator):
var = orig_def.var
else:
var = orig_def
partial_types = self.find_partial_types(var)
if partial_types is not None:
var.type = new_type
del partial_types[var]
else:
# Trying to redefine something like partial empty list as function.
self.fail(message_registry.INCOMPATIBLE_REDEFINITION, defn)
else:
name_expr = NameExpr(defn.name)
name_expr.node = defn.original_def
self.binder.assign_type(name_expr, new_type, orig_type)
self.check_subtype(
new_type,
orig_type,
defn,
message_registry.INCOMPATIBLE_REDEFINITION,
"redefinition with type",
"original type",
)

@contextmanager
def enter_attribute_inference_context(self) -> Iterator[None]:
old_types = self.inferred_attribute_types
Expand All @@ -1159,6 +1163,7 @@ def check_func_def(
) -> None:
"""Type check a function definition."""
# Expand type variables with value restrictions to ordinary types.
self.check_typevar_defaults(typ.variables)
expanded = self.expand_typevars(defn, typ)
original_typ = typ
for item, typ in expanded:
Expand Down Expand Up @@ -2483,6 +2488,8 @@ def visit_class_def(self, defn: ClassDef) -> None:
context=defn,
code=codes.TYPE_VAR,
)
if typ.defn.type_vars:
self.check_typevar_defaults(typ.defn.type_vars)

if typ.is_protocol and typ.defn.type_vars:
self.check_protocol_variance(defn)
Expand Down Expand Up @@ -2546,6 +2553,15 @@ def check_init_subclass(self, defn: ClassDef) -> None:
# all other bases have already been checked.
break

def check_typevar_defaults(self, tvars: Sequence[TypeVarLikeType]) -> None:
for tv in tvars:
if not (isinstance(tv, TypeVarType) and tv.has_default()):
continue
if not is_subtype(tv.default, tv.upper_bound):
self.fail("TypeVar default must be a subtype of the bound type", tv)
if tv.values and not any(tv.default == value for value in tv.values):
self.fail("TypeVar default must be one of the constraint types", tv)

def check_enum(self, defn: ClassDef) -> None:
assert defn.info.is_enum
if defn.info.fullname not in ENUM_BASES:
Expand Down Expand Up @@ -3159,6 +3175,14 @@ def check_assignment(
# Don't use type binder for definitions of special forms, like named tuples.
if not (isinstance(lvalue, NameExpr) and lvalue.is_special_form):
self.binder.assign_type(lvalue, rvalue_type, lvalue_type, False)
if (
isinstance(lvalue, NameExpr)
and isinstance(lvalue.node, Var)
and lvalue.node.is_inferred
and lvalue.node.is_index_var
and lvalue_type is not None
):
lvalue.node.type = remove_instance_last_known_values(lvalue_type)

elif index_lvalue:
self.check_indexed_assignment(index_lvalue, rvalue, lvalue)
Expand All @@ -3168,6 +3192,7 @@ def check_assignment(
rvalue_type = self.expr_checker.accept(rvalue, type_context=type_context)
if not (
inferred.is_final
or inferred.is_index_var
or (isinstance(lvalue, NameExpr) and lvalue.name == "__match_args__")
):
rvalue_type = remove_instance_last_known_values(rvalue_type)
Expand Down Expand Up @@ -5108,6 +5133,10 @@ def visit_decorator_inner(self, e: Decorator, allow_empty: bool = False) -> None
if e.type and not isinstance(get_proper_type(e.type), (FunctionLike, AnyType)):
self.fail(message_registry.BAD_CONSTRUCTOR_TYPE, e)

if e.func.original_def and isinstance(sig, FunctionLike):
# Function definition overrides function definition.
self.check_func_def_override(e.func, sig)

def check_for_untyped_decorator(
self, func: FuncDef, dec_type: Type, dec_expr: Expression
) -> None:
Expand Down Expand Up @@ -5365,6 +5394,9 @@ def remove_capture_conflicts(self, type_map: TypeMap, inferred_types: dict[Var,
del type_map[expr]

def visit_type_alias_stmt(self, o: TypeAliasStmt) -> None:
if o.alias_node:
self.check_typevar_defaults(o.alias_node.alias_tvars)

with self.msg.filter_errors():
self.expr_checker.accept(o.value)

Expand Down
19 changes: 17 additions & 2 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,8 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type:
# TODO: always do this in type_object_type by passing the original context
result.ret_type.line = e.line
result.ret_type.column = e.column
if isinstance(get_proper_type(self.type_context[-1]), TypeType):
# This is the type in a Type[] expression, so substitute type
if is_type_type_context(self.type_context[-1]):
# This is the type in a type[] expression, so substitute type
# variables with Any.
result = erasetype.erase_typevars(result)
elif isinstance(node, MypyFile):
Expand Down Expand Up @@ -5809,6 +5809,12 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F
context=if_type_fallback,
allow_none_return=allow_none_return,
)

# In most cases using if_type as a context for right branch gives better inferred types.
# This is however not the case for literal types, so use the full context instead.
if is_literal_type_like(full_context_else_type) and not is_literal_type_like(else_type):
else_type = full_context_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
Expand Down Expand Up @@ -6621,3 +6627,12 @@ def get_partial_instance_type(t: Type | None) -> PartialType | None:
if t is None or not isinstance(t, PartialType) or t.type is None:
return None
return t


def is_type_type_context(context: Type | None) -> bool:
context = get_proper_type(context)
if isinstance(context, TypeType):
return True
if isinstance(context, UnionType):
return any(is_type_type_context(item) for item in context.items)
return False
8 changes: 0 additions & 8 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -1029,18 +1029,10 @@ def visit_callable_type(self, template: CallableType) -> list[Constraint]:
if template.type_guard is not None and cactual.type_guard is not None:
template_ret_type = template.type_guard
cactual_ret_type = cactual.type_guard
elif template.type_guard is not None:
template_ret_type = AnyType(TypeOfAny.special_form)
elif cactual.type_guard is not None:
cactual_ret_type = AnyType(TypeOfAny.special_form)

if template.type_is is not None and cactual.type_is is not None:
template_ret_type = template.type_is
cactual_ret_type = cactual.type_is
elif template.type_is is not None:
template_ret_type = AnyType(TypeOfAny.special_form)
elif cactual.type_is is not None:
cactual_ret_type = AnyType(TypeOfAny.special_form)

res.extend(infer_constraints(template_ret_type, cactual_ret_type, self.direction))

Expand Down
Loading

0 comments on commit bbaf9de

Please sign in to comment.