Skip to content

Commit 03c7a5a

Browse files
davidfstrclaude
andcommitted
TypeForm: Early-reject Var-with-concrete-Instance-type in try_parse_as_type_expression()
Add a fast-rejection filter to SemanticAnalyzer.try_parse_as_type_expression(): a string literal that is an identifier naming a Var whose declared type is a concrete Instance (and is not a typing special form) is a value -- a local, parameter, or module-level constant -- never a type expression. Reject it before the expensive full-parse block (expr_to_analyzed_type + isolated_error_analysis). On the mypy self-check this filter rejects 157 of the 381 identifier-string literals that currently reach the full-parse block (e.g. "__doc__", "__name__", enum/constant members like "ROUND_DOWN", "GEN_CREATED"), all of which were failing full parses -- pure wasted work. Insertion point chosen empirically. The filter is placed AFTER the existing PlaceholderNode and unbound-tvar checks rather than before them. Its only expensive conjunct (get_proper_type(node.type)) runs solely for Var nodes, and all Var nodes already survive both earlier checks, so position cannot change how often the expensive part runs -- only how often the cheap isinstance(node, Var) conjunct is evaluated. Because 951 of the identifier-strings reaching this block are unbound type variables, evaluating the filter before the tvar check would force an extra isinstance onto ~951 nodes it can never catch. perf_compare.py (--metric cpu --workers1 --num-runs 100) confirms: paired median vs baseline was -15.6ms +/-4.6 here, vs -12.9ms (before placeholder) and -9.3ms (before tvar) -- matching the eval-count model's predicted ordering. Also add typing.Self / typing_extensions.Self to var_is_typing_special_form(). Self is a _SpecialForm-typed Var, so without this guard the new filter would wrongly reject a stringified "Self" type annotation (regressing testSelfRecognizedInOtherSyntacticLocations). This guard is a correctness prerequisite of the filter and is committed together with it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c2ae176 commit 03c7a5a

1 file changed

Lines changed: 12 additions & 0 deletions

File tree

mypy/semanal.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8116,6 +8116,16 @@ def try_parse_as_type_expression(self, maybe_type_expr: Expression) -> None:
81168116
# 2. unbound_paramspec: f'ParamSpec "{name}" is unbound' [codes.VALID_TYPE]
81178117
maybe_type_expr.as_type = None
81188118
return
8119+
if (
8120+
isinstance(node, Var)
8121+
and isinstance(get_proper_type(node.type), Instance)
8122+
and not self.var_is_typing_special_form(node)
8123+
):
8124+
# Var whose declared type is a concrete instance: it is
8125+
# a value (local, parameter, module-level constant),
8126+
# not a type expression.
8127+
maybe_type_expr.as_type = None
8128+
return
81198129
else: # does not look like an identifier
81208130
if '"' in str_value or "'" in str_value:
81218131
# Only valid inside a Literal[...] or Annotated[..., ...] type
@@ -8208,6 +8218,8 @@ def var_is_typing_special_form(var: Var) -> bool:
82088218
"typing.Literal",
82098219
"typing_extensions.Literal",
82108220
"typing.Optional",
8221+
"typing.Self",
8222+
"typing_extensions.Self",
82118223
"typing.TypeGuard",
82128224
"typing_extensions.TypeGuard",
82138225
"typing.TypeIs",

0 commit comments

Comments
 (0)