Skip to content

Commit 37481a2

Browse files
committed
Merge branch 'master' into closed.keyword
2 parents 8d92a10 + 47ca4c2 commit 37481a2

57 files changed

Lines changed: 1623 additions & 633 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

misc/dump-ast.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def dump(fname: str, python_version: tuple[int, int], quiet: bool = False) -> No
1919
options.python_version = python_version
2020
with open(fname, "rb") as f:
2121
s = f.read()
22-
tree = parse(s, fname, None, errors=Errors(options), options=options, file_exists=True)
22+
tree = parse(s, fname, None, errors=Errors(options), options=options)
2323
if not quiet:
2424
print(tree)
2525

mypy/applytype.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ def get_target_type(
3737
report_incompatible_typevar_value: Callable[[CallableType, Type, str, Context], None],
3838
context: Context,
3939
skip_unsatisfied: bool,
40+
id_to_type: dict[TypeVarId, Type],
4041
) -> Type | None:
4142
p_type = get_proper_type(type)
4243
if isinstance(p_type, UninhabitedType) and p_type.ambiguous and tvar.has_default():
43-
return tvar.default
44+
# Gradually expand defaults, as they may depend on previous type variables.
45+
return expand_type(tvar.default, id_to_type)
4446
if isinstance(tvar, ParamSpecType):
4547
return type
4648
if isinstance(tvar, TypeVarTupleType):
@@ -113,7 +115,13 @@ def apply_generic_arguments(
113115
continue
114116

115117
target_type = get_target_type(
116-
tvar, type, callable, report_incompatible_typevar_value, context, skip_unsatisfied
118+
tvar,
119+
type,
120+
callable,
121+
report_incompatible_typevar_value,
122+
context,
123+
skip_unsatisfied,
124+
id_to_type,
117125
)
118126
if target_type is not None:
119127
id_to_type[tvar.id] = target_type

mypy/build.py

Lines changed: 65 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,85 +1024,76 @@ def parse_all(self, states: list[State], post_parse: bool = True) -> None:
10241024
self.post_parse_all(states)
10251025
return
10261026

1027-
sequential_states = []
10281027
parallel_states = []
10291028
for state in states:
1029+
if not self.fscache.exists(state.xpath, real_only=True):
1030+
state.source = state.get_source()
10301031
if state.tree is not None:
10311032
# The file was already parsed.
1032-
continue
1033-
if not self.fscache.exists(state.xpath, real_only=True):
1034-
# New parser only supports parsing on-disk files.
1035-
sequential_states.append(state)
1033+
state.needs_parse = False
10361034
continue
10371035
parallel_states.append(state)
1036+
10381037
if len(parallel_states) > 1:
1039-
self.parse_parallel(sequential_states, parallel_states)
1040-
else:
1041-
# Avoid using executor when there is no parallelism.
1042-
for state in states:
1043-
state.parse_file()
1044-
if post_parse:
1045-
self.post_parse_all(states)
1038+
# This duplicates a bit of logic from State.parse_file(). This is done as an
1039+
# optimization to parallelize only those parts of the code that can be
1040+
# parallelized efficiently.
10461041

1047-
def parse_parallel(self, sequential_states: list[State], parallel_states: list[State]) -> None:
1048-
"""Perform parallel parsing of states.
1042+
parallel_parsed_states, parallel_parsed_states_set = self.parse_files_threaded_raw(
1043+
parallel_states
1044+
)
10491045

1050-
Note: this duplicates a bit of logic from State.parse_file(). This is done
1051-
as an optimization to parallelize only those parts of the code that can be
1052-
parallelized efficiently.
1053-
"""
1054-
parallel_parsed_states, parallel_parsed_states_set = self.parse_files_threaded_raw(
1055-
sequential_states, parallel_states
1056-
)
1046+
for state in parallel_parsed_states:
1047+
# New parser only returns serialized ASTs
1048+
with state.wrap_context():
1049+
assert state.tree is not None
1050+
raw_data = state.tree.raw_data
1051+
if raw_data is not None:
1052+
# Apply inline mypy config before deserialization, since
1053+
# some options (e.g. implicit_optional) affect how the
1054+
# AST is built during deserialization.
1055+
state.source_hash = raw_data.source_hash
1056+
state.apply_inline_configuration(raw_data.mypy_comments)
1057+
state.tree = load_from_raw(
1058+
state.xpath,
1059+
state.id,
1060+
raw_data,
1061+
self.errors,
1062+
state.options,
1063+
imports_only=bool(self.workers),
1064+
)
1065+
if self.errors.is_blockers():
1066+
self.log("Bailing due to parse errors")
1067+
self.errors.raise_error()
10571068

1058-
for state in parallel_parsed_states:
1059-
# New parser returns serialized ASTs. Deserialize full trees only if not using
1060-
# parallel workers.
1061-
with state.wrap_context():
1069+
for state in parallel_states:
10621070
assert state.tree is not None
1063-
raw_data = state.tree.raw_data
1064-
if raw_data is not None:
1065-
# Apply inline mypy config before deserialization, since
1066-
# some options (e.g. implicit_optional) affect deserialization
1067-
state.source_hash = raw_data.source_hash
1068-
state.apply_inline_configuration(raw_data.mypy_comments)
1069-
state.tree = load_from_raw(
1070-
state.xpath,
1071-
state.id,
1072-
raw_data,
1073-
self.errors,
1074-
state.options,
1075-
imports_only=bool(self.workers),
1076-
)
1077-
if self.errors.is_blockers():
1078-
self.log("Bailing due to parse errors")
1079-
self.errors.raise_error()
1080-
1081-
for state in parallel_states:
1082-
assert state.tree is not None
1083-
if state in parallel_parsed_states_set:
1071+
if state in parallel_parsed_states_set:
1072+
if state.tree.raw_data is not None:
1073+
# source_hash was already extracted above, but raw_data
1074+
# may have been preserved for workers (imports_only=True).
1075+
pass
1076+
elif state.source_hash is None:
1077+
# At least namespace packages may not have source.
1078+
state.get_source()
1079+
state.early_errors = list(self.errors.error_info_map.get(state.xpath, []))
1080+
state.semantic_analysis_pass1()
1081+
self.ast_cache[state.id] = (state.tree, state.early_errors, state.source_hash)
1082+
self.modules[state.id] = state.tree
10841083
if state.tree.raw_data is not None:
1085-
# source_hash was already extracted above, but raw_data
1086-
# may have been preserved for workers (imports_only=True).
1087-
pass
1088-
elif state.source_hash is None:
1089-
# At least namespace packages may not have source.
1090-
state.get_source()
1091-
state.early_errors = list(self.errors.error_info_map.get(state.xpath, []))
1092-
state.semantic_analysis_pass1()
1093-
self.ast_cache[state.id] = (state.tree, state.early_errors, state.source_hash)
1094-
self.modules[state.id] = state.tree
1095-
if state.tree.raw_data is not None:
1096-
state.size_hint = len(state.tree.raw_data.defs) + MIN_SIZE_HINT
1097-
state.check_blockers()
1098-
state.setup_errors()
1099-
1100-
def parse_files_threaded_raw(
1101-
self, sequential_states: list[State], parallel_states: list[State]
1102-
) -> tuple[list[State], set[State]]:
1103-
"""Parse files using a thread pool.
1104-
1105-
Also parse sequential states while waiting for the parallel results.
1084+
state.size_hint = len(state.tree.raw_data.defs) + MIN_SIZE_HINT
1085+
state.check_blockers()
1086+
state.setup_errors()
1087+
elif len(parallel_states) == 1:
1088+
# Avoid using executor when there is no parallelism.
1089+
parallel_states[0].parse_file()
1090+
1091+
if post_parse:
1092+
self.post_parse_all(states)
1093+
1094+
def parse_files_threaded_raw(self, states: list[State]) -> tuple[list[State], set[State]]:
1095+
"""Parse files in parallel using a thread pool.
1096+
11061097
Trees from the new parser are left in raw (serialized) form.
11071098
11081099
Return (list, set) of states that were actually parsed (not cached).
@@ -1118,25 +1109,21 @@ def parse_files_threaded_raw(
11181109
# parse_file_inner() results in no visible improvement with more than 8 threads.
11191110
# TODO: reuse thread pool and/or batch small files in single submit() call.
11201111
with ThreadPoolExecutor(max_workers=min(available_threads, 8)) as executor:
1121-
for state in parallel_states:
1112+
for state in states:
11221113
state.needs_parse = False
11231114
if state.id not in self.ast_cache:
11241115
self.log(f"Parsing {state.xpath} ({state.id})")
11251116
ignore_errors = state.ignore_all or state.options.ignore_errors
11261117
if ignore_errors:
11271118
self.errors.ignored_files.add(state.xpath)
1128-
futures.append(executor.submit(state.parse_file_inner, ""))
1119+
futures.append(executor.submit(state.parse_file_inner, state.source))
11291120
parallel_parsed_states.append(state)
11301121
parallel_parsed_states_set.add(state)
11311122
else:
11321123
self.log(f"Using cached AST for {state.xpath} ({state.id})")
11331124
state.tree, state.early_errors, source_hash = self.ast_cache[state.id]
11341125
state.source_hash = source_hash
11351126

1136-
# Parse sequential before waiting on parallel.
1137-
for state in sequential_states:
1138-
state.parse_file()
1139-
11401127
for fut in wait(futures).done:
11411128
fut.result()
11421129

@@ -1279,21 +1266,20 @@ def parse_file(
12791266
self,
12801267
id: str,
12811268
path: str,
1282-
source: str,
1269+
source: str | None,
12831270
options: Options,
12841271
raw_data: FileRawData | None = None,
12851272
) -> MypyFile:
12861273
"""Parse the source of a file with the given name.
12871274
12881275
Raise CompileError if there is a parse error.
12891276
"""
1290-
file_exists = self.fscache.exists(path, real_only=True)
12911277
t0 = time.time()
12921278
if raw_data:
12931279
# If possible, deserialize from known binary data instead of parsing from scratch.
12941280
tree = load_from_raw(path, id, raw_data, self.errors, options)
12951281
else:
1296-
tree = parse(source, path, id, self.errors, options=options, file_exists=file_exists)
1282+
tree = parse(source, path, id, self.errors, options=options)
12971283
tree._fullname = id
12981284
if self.stats_enabled:
12991285
with self.stats_lock:
@@ -3179,7 +3165,7 @@ def get_source(self) -> str:
31793165
else:
31803166
err = f"{self.path}: error: Cannot decode file: {str(decodeerr)}"
31813167
raise CompileError([err], module_with_blocker=self.id) from decodeerr
3182-
elif self.path and self.manager.fscache.isdir(self.path):
3168+
elif self.path and manager.fscache.isdir(self.path):
31833169
source = ""
31843170
self.source_hash = ""
31853171
else:
@@ -3192,7 +3178,7 @@ def get_source(self) -> str:
31923178
self.time_spent_us += time_spent_us(t0)
31933179
return source
31943180

3195-
def parse_file_inner(self, source: str, raw_data: FileRawData | None = None) -> None:
3181+
def parse_file_inner(self, source: str | None, raw_data: FileRawData | None = None) -> None:
31963182
t0 = time_ref()
31973183
self.tree = self.manager.parse_file(
31983184
self.id, self.xpath, source, options=self.options, raw_data=raw_data
@@ -3319,9 +3305,7 @@ def semantic_analysis_pass1(self) -> None:
33193305
#
33203306
# TODO: This should not be considered as a semantic analysis
33213307
# pass -- it's an independent pass.
3322-
if not options.native_parser or not self.manager.fscache.exists(
3323-
self.xpath, real_only=True
3324-
):
3308+
if not options.native_parser:
33253309
analyzer = SemanticAnalyzerPreAnalysis()
33263310
with self.wrap_context():
33273311
analyzer.visit_file(self.tree, self.xpath, self.id, options)

mypy/checker.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8581,6 +8581,9 @@ def lookup_fully_qualified_or_none(self, fullname: str, /) -> SymbolTableNode |
85818581
except KeyError:
85828582
return None
85838583

8584+
def record_fixed_type(self, fixed: TypeInfo | TypeAlias) -> None:
8585+
pass
8586+
85848587
def fail(
85858588
self,
85868589
msg: str,
@@ -8629,6 +8632,9 @@ def is_func_scope(self) -> bool:
86298632
# message types are ignored.
86308633
return False
86318634

8635+
def is_nested_within_func_scope(self) -> bool:
8636+
return self._chk.scope.top_level_function() is not None
8637+
86328638
@property
86338639
def type(self) -> TypeInfo | None:
86348640
return self._chk.scope.current_class()

mypy/checkexpr.py

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -949,7 +949,8 @@ def match_typeddict_call_with_dict(
949949
kwargs: list[tuple[Expression | None, Expression]],
950950
context: Context,
951951
) -> bool:
952-
result = self.validate_typeddict_kwargs(kwargs=kwargs, callee=callee)
952+
with self.msg.filter_errors():
953+
result = self.validate_typeddict_kwargs(kwargs=kwargs, callee=callee)
953954
if result is not None:
954955
validated_kwargs, _ = result
955956
return callee.required_keys <= set(validated_kwargs.keys()) <= set(callee.items.keys())
@@ -1764,6 +1765,28 @@ def check_callable_call(
17641765
lambda i: self.accept(args[i]),
17651766
)
17661767

1768+
if callee.special_sig == "tuple" and len(args) == 1:
1769+
with self.msg.filter_errors():
1770+
arg_type = get_proper_type(self.accept(args[0]))
1771+
# Give precise constructor signature for situations like this:
1772+
# class Shape[*Ts](tuple[*Ts]): ...
1773+
# Shape((1, 2))
1774+
# The argument type is the same as return type, but with builtins.tuple fallback.
1775+
if isinstance(arg_type, TupleType):
1776+
assert isinstance(callee.ret_type, ProperType)
1777+
if isinstance(callee.ret_type, TupleType):
1778+
# Actual type argument is ignored by tuple_fallback() in this case.
1779+
any_type = AnyType(TypeOfAny.special_form)
1780+
new_arg_type = callee.ret_type.copy_modified(
1781+
fallback=self.chk.named_generic_type("builtins.tuple", [any_type])
1782+
)
1783+
callee = callee.copy_modified(arg_types=[new_arg_type])
1784+
elif isinstance(callee.ret_type, Instance):
1785+
new_arg_type = map_instance_to_supertype(
1786+
callee.ret_type, self.chk.lookup_typeinfo("builtins.tuple")
1787+
)
1788+
callee = callee.copy_modified(arg_types=[new_arg_type])
1789+
17671790
if callee.is_generic():
17681791
need_refresh = any(
17691792
isinstance(v, (ParamSpecType, TypeVarTupleType)) for v in callee.variables
@@ -4932,10 +4955,11 @@ def visit_type_application(self, tapp: TypeApplication) -> Type:
49324955
if tapp.expr.node.python_3_12_type_alias:
49334956
return self.type_alias_type_type()
49344957
# Subscription of a (generic) alias in runtime context, expand the alias.
4935-
item = instantiate_type_alias(
4958+
item, _ = instantiate_type_alias(
49364959
tapp.expr.node,
49374960
tapp.types,
49384961
self.chk.fail,
4962+
self.chk.note,
49394963
tapp.expr.node.no_args,
49404964
tapp,
49414965
self.chk.options,
@@ -5000,17 +5024,16 @@ class LongName(Generic[T]): ...
50005024
# A = List[Tuple[T, T]]
50015025
# x = A() <- same as List[Tuple[Any, Any]], see PEP 484.
50025026
disallow_any = self.chk.options.disallow_any_generics and self.is_callee
5003-
item = get_proper_type(
5004-
set_any_tvars(
5005-
alias,
5006-
[],
5007-
ctx.line,
5008-
ctx.column,
5009-
self.chk.options,
5010-
disallow_any=disallow_any,
5011-
fail=self.msg.fail,
5012-
)
5027+
item, _ = set_any_tvars(
5028+
alias,
5029+
[],
5030+
ctx.line,
5031+
ctx.column,
5032+
self.chk.options,
5033+
disallow_any=disallow_any,
5034+
fail=self.msg.fail,
50135035
)
5036+
item = get_proper_type(item)
50145037
if isinstance(item, Instance):
50155038
# Normally we get a callable type (or overloaded) with .is_type_obj() true
50165039
# representing the class's constructor
@@ -5073,11 +5096,7 @@ class C(Generic[T, Unpack[Ts]]): ...
50735096
return [AnyType(TypeOfAny.from_error)] * len(vars)
50745097

50755098
# TODO: in future we may want to support type application to variadic functions.
5076-
if (
5077-
not vars
5078-
or not any(isinstance(v, TypeVarTupleType) for v in vars)
5079-
or not t.is_type_obj()
5080-
):
5099+
if not vars or not t.is_type_obj() or t.type_object().fullname == "builtins.tuple":
50815100
return list(args)
50825101
info = t.type_object()
50835102
# We reuse the logic from semanal phase to reduce code duplication.
@@ -5091,6 +5110,9 @@ class C(Generic[T, Unpack[Ts]]): ...
50915110
)
50925111
args = list(fake.args)
50935112

5113+
if not any(isinstance(v, TypeVarTupleType) for v in vars):
5114+
return args
5115+
50945116
prefix = next(i for (i, v) in enumerate(vars) if isinstance(v, TypeVarTupleType))
50955117
suffix = len(vars) - prefix - 1
50965118
tvt = vars[prefix]

mypy/checkstrformat.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,6 @@ def apply_field_accessors(
587587
module=None,
588588
options=self.chk.options,
589589
errors=temp_errors,
590-
file_exists=False,
591590
eager=True,
592591
)
593592
if temp_errors.is_errors():

0 commit comments

Comments
 (0)