Skip to content

Commit 28e5436

Browse files
authored
Refactoring: make the type of fullname str instead of Bogus[str] (#14435)
The type Bogus[X] is treated as Any when the code is compiled with mypyc, while it's equivalent to X when only type checking. They are sometimes used when X is not actually the real type of a value, but changing it to the correct type would be complicated. Bogus[str] types are pretty awkward, since we are lying to the type checker and mypyc only sees Any types. An empty fullname is now represented by "" instead of None, so we no longer need a Bogus[str] type. This might break some plugins, so we should document this in release notes and the relevant issue that tracks plugin incompatibilities. (Various small optimizations, including this, together netted a 6% performance improvement in self check.)
1 parent 1c5eeb8 commit 28e5436

15 files changed

+65
-61
lines changed

mypy/checker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2178,7 +2178,7 @@ def visit_class_def(self, defn: ClassDef) -> None:
21782178
temp = self.temp_node(sig, context=decorator)
21792179
fullname = None
21802180
if isinstance(decorator, RefExpr):
2181-
fullname = decorator.fullname
2181+
fullname = decorator.fullname or None
21822182

21832183
# TODO: Figure out how to have clearer error messages.
21842184
# (e.g. "class decorator must be a function that accepts a type."
@@ -4598,7 +4598,7 @@ def visit_decorator(self, e: Decorator) -> None:
45984598
temp = self.temp_node(sig, context=e)
45994599
fullname = None
46004600
if isinstance(d, RefExpr):
4601-
fullname = d.fullname
4601+
fullname = d.fullname or None
46024602
# if this is a expression like @b.a where b is an object, get the type of b
46034603
# so we can pass it the method hook in the plugins
46044604
object_type: Type | None = None

mypy/checkexpr.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ def extract_refexpr_names(expr: RefExpr) -> set[str]:
216216
Note that currently, the only two subclasses of RefExpr are NameExpr and
217217
MemberExpr."""
218218
output: set[str] = set()
219-
while isinstance(expr.node, MypyFile) or expr.fullname is not None:
220-
if isinstance(expr.node, MypyFile) and expr.fullname is not None:
219+
while isinstance(expr.node, MypyFile) or expr.fullname:
220+
if isinstance(expr.node, MypyFile) and expr.fullname:
221221
# If it's None, something's wrong (perhaps due to an
222222
# import cycle or a suppressed error). For now we just
223223
# skip it.
@@ -228,7 +228,7 @@ def extract_refexpr_names(expr: RefExpr) -> set[str]:
228228
if isinstance(expr.node, TypeInfo):
229229
# Reference to a class or a nested class
230230
output.update(split_module_names(expr.node.module_name))
231-
elif expr.fullname is not None and "." in expr.fullname and not is_suppressed_import:
231+
elif "." in expr.fullname and not is_suppressed_import:
232232
# Everything else (that is not a silenced import within a class)
233233
output.add(expr.fullname.rsplit(".", 1)[0])
234234
break
@@ -526,7 +526,7 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) ->
526526
# There are two special cases where plugins might act:
527527
# * A "static" reference/alias to a class or function;
528528
# get_function_hook() will be invoked for these.
529-
fullname = e.callee.fullname
529+
fullname = e.callee.fullname or None
530530
if isinstance(e.callee.node, TypeAlias):
531531
target = get_proper_type(e.callee.node.target)
532532
if isinstance(target, Instance):
@@ -536,7 +536,7 @@ def visit_call_expr_inner(self, e: CallExpr, allow_none_return: bool = False) ->
536536
# get_method_hook() and get_method_signature_hook() will
537537
# be invoked for these.
538538
if (
539-
fullname is None
539+
not fullname
540540
and isinstance(e.callee, MemberExpr)
541541
and self.chk.has_type(e.callee.expr)
542542
):
@@ -605,7 +605,7 @@ def method_fullname(self, object_type: Type, method_name: str) -> str | None:
605605
elif isinstance(object_type, TupleType):
606606
type_name = tuple_fallback(object_type).type.fullname
607607

608-
if type_name is not None:
608+
if type_name:
609609
return f"{type_name}.{method_name}"
610610
else:
611611
return None
@@ -5489,7 +5489,7 @@ def type_info_from_type(typ: Type) -> TypeInfo | None:
54895489

54905490

54915491
def is_operator_method(fullname: str | None) -> bool:
5492-
if fullname is None:
5492+
if not fullname:
54935493
return False
54945494
short_name = fullname.split(".")[-1]
54955495
return (

mypy/nodes.py

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
from mypy_extensions import trait
2626

2727
import mypy.strconv
28-
from mypy.bogus_type import Bogus
2928
from mypy.util import short_type
3029
from mypy.visitor import ExpressionVisitor, NodeVisitor, StatementVisitor
3130

@@ -247,12 +246,10 @@ class SymbolNode(Node):
247246
def name(self) -> str:
248247
pass
249248

250-
# fullname can often be None even though the type system
251-
# disagrees. We mark this with Bogus to let mypyc know not to
252-
# worry about it.
249+
# Fully qualified name
253250
@property
254251
@abstractmethod
255-
def fullname(self) -> Bogus[str]:
252+
def fullname(self) -> str:
256253
pass
257254

258255
@abstractmethod
@@ -294,7 +291,7 @@ class MypyFile(SymbolNode):
294291
__match_args__ = ("name", "path", "defs")
295292

296293
# Fully qualified module name
297-
_fullname: Bogus[str]
294+
_fullname: str
298295
# Path to the file (empty string if not known)
299296
path: str
300297
# Top-level definitions and statements
@@ -361,7 +358,7 @@ def name(self) -> str:
361358
return "" if not self._fullname else self._fullname.split(".")[-1]
362359

363360
@property
364-
def fullname(self) -> Bogus[str]:
361+
def fullname(self) -> str:
365362
return self._fullname
366363

367364
def accept(self, visitor: NodeVisitor[T]) -> T:
@@ -526,16 +523,15 @@ def __init__(self) -> None:
526523
self.is_static = False
527524
self.is_final = False
528525
# Name with module prefix
529-
# TODO: Type should be Optional[str]
530-
self._fullname = cast(Bogus[str], None)
526+
self._fullname = ""
531527

532528
@property
533529
@abstractmethod
534530
def name(self) -> str:
535531
pass
536532

537533
@property
538-
def fullname(self) -> Bogus[str]:
534+
def fullname(self) -> str:
539535
return self._fullname
540536

541537

@@ -871,7 +867,7 @@ def name(self) -> str:
871867
return self.func.name
872868

873869
@property
874-
def fullname(self) -> Bogus[str]:
870+
def fullname(self) -> str:
875871
return self.func.fullname
876872

877873
@property
@@ -967,7 +963,7 @@ def __init__(self, name: str, type: mypy.types.Type | None = None) -> None:
967963
super().__init__()
968964
self._name = name # Name without module prefix
969965
# TODO: Should be Optional[str]
970-
self._fullname = cast("Bogus[str]", None) # Name with module prefix
966+
self._fullname = "" # Name with module prefix
971967
# TODO: Should be Optional[TypeInfo]
972968
self.info = VAR_NO_INFO
973969
self.type: mypy.types.Type | None = type # Declared or inferred type, or None
@@ -1019,7 +1015,7 @@ def name(self) -> str:
10191015
return self._name
10201016

10211017
@property
1022-
def fullname(self) -> Bogus[str]:
1018+
def fullname(self) -> str:
10231019
return self._fullname
10241020

10251021
def accept(self, visitor: NodeVisitor[T]) -> T:
@@ -1057,7 +1053,7 @@ class ClassDef(Statement):
10571053

10581054
__slots__ = (
10591055
"name",
1060-
"fullname",
1056+
"_fullname",
10611057
"defs",
10621058
"type_vars",
10631059
"base_type_exprs",
@@ -1075,7 +1071,7 @@ class ClassDef(Statement):
10751071
__match_args__ = ("name", "defs")
10761072

10771073
name: str # Name of the class without module prefix
1078-
fullname: Bogus[str] # Fully qualified name of the class
1074+
_fullname: str # Fully qualified name of the class
10791075
defs: Block
10801076
type_vars: list[mypy.types.TypeVarLikeType]
10811077
# Base class expressions (not semantically analyzed -- can be arbitrary expressions)
@@ -1102,7 +1098,7 @@ def __init__(
11021098
) -> None:
11031099
super().__init__()
11041100
self.name = name
1105-
self.fullname = None # type: ignore[assignment]
1101+
self._fullname = ""
11061102
self.defs = defs
11071103
self.type_vars = type_vars or []
11081104
self.base_type_exprs = base_type_exprs or []
@@ -1117,6 +1113,14 @@ def __init__(
11171113
self.deco_line: int | None = None
11181114
self.removed_statements = []
11191115

1116+
@property
1117+
def fullname(self) -> str:
1118+
return self._fullname
1119+
1120+
@fullname.setter
1121+
def fullname(self, v: str) -> None:
1122+
self._fullname = v
1123+
11201124
def accept(self, visitor: StatementVisitor[T]) -> T:
11211125
return visitor.visit_class_def(self)
11221126

@@ -1725,7 +1729,7 @@ class RefExpr(Expression):
17251729
__slots__ = (
17261730
"kind",
17271731
"node",
1728-
"fullname",
1732+
"_fullname",
17291733
"is_new_def",
17301734
"is_inferred_def",
17311735
"is_alias_rvalue",
@@ -1739,7 +1743,7 @@ def __init__(self) -> None:
17391743
# Var, FuncDef or TypeInfo that describes this
17401744
self.node: SymbolNode | None = None
17411745
# Fully qualified name (or name if not global)
1742-
self.fullname: str | None = None
1746+
self._fullname = ""
17431747
# Does this define a new name?
17441748
self.is_new_def = False
17451749
# Does this define a new name with inferred type?
@@ -1752,6 +1756,14 @@ def __init__(self) -> None:
17521756
# Cache type guard from callable_type.type_guard
17531757
self.type_guard: mypy.types.Type | None = None
17541758

1759+
@property
1760+
def fullname(self) -> str:
1761+
return self._fullname
1762+
1763+
@fullname.setter
1764+
def fullname(self, v: str) -> None:
1765+
self._fullname = v
1766+
17551767

17561768
class NameExpr(RefExpr):
17571769
"""Name expression
@@ -2806,7 +2818,7 @@ class is generic then it will be a type constructor of higher kind.
28062818
"self_type",
28072819
)
28082820

2809-
_fullname: Bogus[str] # Fully qualified name
2821+
_fullname: str # Fully qualified name
28102822
# Fully qualified name for the module this type was defined in. This
28112823
# information is also in the fullname, but is harder to extract in the
28122824
# case of nested class definitions.
@@ -3023,7 +3035,7 @@ def name(self) -> str:
30233035
return self.defn.name
30243036

30253037
@property
3026-
def fullname(self) -> Bogus[str]:
3038+
def fullname(self) -> str:
30273039
return self._fullname
30283040

30293041
def is_generic(self) -> bool:
@@ -3739,11 +3751,7 @@ def serialize(self, prefix: str, name: str) -> JsonDict:
37393751
if prefix is not None:
37403752
fullname = self.node.fullname
37413753
if (
3742-
# See the comment above SymbolNode.fullname -- fullname can often be None,
3743-
# but for complex reasons it's annotated as being `Bogus[str]` instead of `str | None`,
3744-
# meaning mypy erroneously thinks the `fullname is not None` check here is redundant
3745-
fullname is not None # type: ignore[redundant-expr]
3746-
and "." in fullname
3754+
"." in fullname
37473755
and fullname != prefix + "." + name
37483756
and not (isinstance(self.node, Var) and self.node.from_module_getattr)
37493757
):

mypy/partially_defined.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ def is_undefined(self, name: str) -> bool:
287287

288288

289289
def refers_to_builtin(o: RefExpr) -> bool:
290-
return o.fullname is not None and o.fullname.startswith("builtins.")
290+
return o.fullname.startswith("builtins.")
291291

292292

293293
class Loop:

mypy/semanal.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3022,13 +3022,13 @@ def analyze_lvalues(self, s: AssignmentStmt) -> None:
30223022
def apply_dynamic_class_hook(self, s: AssignmentStmt) -> None:
30233023
if not isinstance(s.rvalue, CallExpr):
30243024
return
3025-
fname = None
3025+
fname = ""
30263026
call = s.rvalue
30273027
while True:
30283028
if isinstance(call.callee, RefExpr):
30293029
fname = call.callee.fullname
30303030
# check if method call
3031-
if fname is None and isinstance(call.callee, MemberExpr):
3031+
if not fname and isinstance(call.callee, MemberExpr):
30323032
callee_expr = call.callee.expr
30333033
if isinstance(callee_expr, RefExpr) and callee_expr.fullname:
30343034
method_name = call.callee.name
@@ -4624,7 +4624,7 @@ def bind_name_expr(self, expr: NameExpr, sym: SymbolTableNode) -> None:
46244624
else:
46254625
expr.kind = sym.kind
46264626
expr.node = sym.node
4627-
expr.fullname = sym.fullname
4627+
expr.fullname = sym.fullname or ""
46284628

46294629
def visit_super_expr(self, expr: SuperExpr) -> None:
46304630
if not self.type and not expr.call.args:
@@ -4849,7 +4849,7 @@ def visit_member_expr(self, expr: MemberExpr) -> None:
48494849
self.process_placeholder(expr.name, "attribute", expr)
48504850
return
48514851
expr.kind = sym.kind
4852-
expr.fullname = sym.fullname
4852+
expr.fullname = sym.fullname or ""
48534853
expr.node = sym.node
48544854
elif isinstance(base, RefExpr):
48554855
# This branch handles the case C.bar (or cls.bar or self.bar inside
@@ -4881,7 +4881,7 @@ def visit_member_expr(self, expr: MemberExpr) -> None:
48814881
if not n:
48824882
return
48834883
expr.kind = n.kind
4884-
expr.fullname = n.fullname
4884+
expr.fullname = n.fullname or ""
48854885
expr.node = n.node
48864886

48874887
def visit_op_expr(self, expr: OpExpr) -> None:
@@ -5341,7 +5341,7 @@ def is_overloaded_item(self, node: SymbolNode, statement: Statement) -> bool:
53415341
return False
53425342

53435343
def is_defined_in_current_module(self, fullname: str | None) -> bool:
5344-
if fullname is None:
5344+
if not fullname:
53455345
return False
53465346
return module_prefix(self.modules, fullname) == self.cur_mod_id
53475347

mypy/server/aststrip.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def visit_op_expr(self, node: OpExpr) -> None:
230230
def strip_ref_expr(self, node: RefExpr) -> None:
231231
node.kind = None
232232
node.node = None
233-
node.fullname = None
233+
node.fullname = ""
234234
node.is_new_def = False
235235
node.is_inferred_def = False
236236

mypy/server/deps.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -289,13 +289,9 @@ def visit_decorator(self, o: Decorator) -> None:
289289
# all call sites, making them all `Any`.
290290
for d in o.decorators:
291291
tname: str | None = None
292-
if isinstance(d, RefExpr) and d.fullname is not None:
292+
if isinstance(d, RefExpr) and d.fullname:
293293
tname = d.fullname
294-
if (
295-
isinstance(d, CallExpr)
296-
and isinstance(d.callee, RefExpr)
297-
and d.callee.fullname is not None
298-
):
294+
if isinstance(d, CallExpr) and isinstance(d.callee, RefExpr) and d.callee.fullname:
299295
tname = d.callee.fullname
300296
if tname is not None:
301297
self.add_dependency(make_trigger(tname), make_trigger(o.func.fullname))
@@ -500,7 +496,7 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None:
500496
if (
501497
isinstance(rvalue, CallExpr)
502498
and isinstance(rvalue.callee, RefExpr)
503-
and rvalue.callee.fullname is not None
499+
and rvalue.callee.fullname
504500
):
505501
fname: str | None = None
506502
if isinstance(rvalue.callee.node, TypeInfo):
@@ -510,7 +506,7 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None:
510506
fname = init.node.fullname
511507
else:
512508
fname = rvalue.callee.fullname
513-
if fname is None:
509+
if not fname:
514510
return
515511
for lv in o.lvalues:
516512
if isinstance(lv, RefExpr) and lv.fullname and lv.is_new_def:
@@ -638,7 +634,7 @@ def visit_del_stmt(self, o: DelStmt) -> None:
638634
# Expressions
639635

640636
def process_global_ref_expr(self, o: RefExpr) -> None:
641-
if o.fullname is not None:
637+
if o.fullname:
642638
self.add_dependency(make_trigger(o.fullname))
643639

644640
# If this is a reference to a type, generate a dependency to its

mypy/strconv.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ def pretty_name(
367367
id = ""
368368
if isinstance(target_node, mypy.nodes.MypyFile) and name == fullname:
369369
n += id
370-
elif kind == mypy.nodes.GDEF or (fullname != name and fullname is not None):
370+
elif kind == mypy.nodes.GDEF or (fullname != name and fullname):
371371
# Append fully qualified name for global references.
372372
n += f" [{fullname}{id}]"
373373
elif kind == mypy.nodes.LDEF:

mypy/stubtest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1147,7 +1147,7 @@ def apply_decorator_to_funcitem(
11471147
) -> nodes.FuncItem | None:
11481148
if not isinstance(decorator, nodes.RefExpr):
11491149
return None
1150-
if decorator.fullname is None:
1150+
if not decorator.fullname:
11511151
# Happens with namedtuple
11521152
return None
11531153
if (

0 commit comments

Comments
 (0)