@@ -1226,15 +1226,18 @@ def is_synthetic_type(self, typ: TypeInfo) -> bool:
12261226 return typ .is_named_tuple or typ .is_newtype or typ .typeddict_type is not None
12271227
12281228 def get_final_ref (self , expr : MemberExpr ) -> tuple [str , Var , bool ] | None :
1229- """Check if `expr` is a final class or module attribute.
1229+ """Check if `expr` is a final class, module or instance attribute.
12301230
1231- Return False for instance attributes.
1232-
1233- This needs to be done differently for class and module attributes to
1231+ This needs to be done differently for class, module and instance attributes to
12341232 correctly determine fully qualified name. Return a tuple that consists of
12351233 the qualified name, the corresponding Var node, and a flag indicating whether
12361234 the final name was defined in a compiled module. Return None if `expr` does not
12371235 refer to a final attribute.
1236+
1237+ Instance attributes only qualify if they are class-body Finals ("X: Final = ..."
1238+ in the class body), which have no instance slot -- see
1239+ ClassIR.class_final_attributes. Ordinary instance attributes, including
1240+ "self.x: Final = ..." set in __init__, return None.
12381241 """
12391242 final_var = None
12401243 if isinstance (expr .expr , RefExpr ) and isinstance (expr .expr .node , TypeInfo ):
@@ -1254,10 +1257,69 @@ def get_final_ref(self, expr: MemberExpr) -> tuple[str, Var, bool] | None:
12541257 final_var = expr .node
12551258 fullname = expr .node .fullname
12561259 native = self .is_native_ref_expr (expr )
1260+ else :
1261+ # Possibly a class-body Final read through an instance ("self.X"). These
1262+ # have no instance slot, so read them exactly like "Cls.X".
1263+ var = self .get_class_final_var (expr )
1264+ if var is not None :
1265+ final_var = var
1266+ fullname = f"{ var .info .fullname } .{ var .name } "
1267+ native = self .is_native_module (var .info .module_name )
12571268 if final_var is not None :
12581269 return fullname , final_var , native
12591270 return None
12601271
1272+ def get_class_final_var (self , expr : MemberExpr ) -> Var | None :
1273+ """Return the Var for a class-body Final read through an instance expression.
1274+
1275+ Return None if `expr` isn't such a read; the caller then falls back to an
1276+ ordinary attribute read, which finds these names on the type object via
1277+ py_get_attr (correct, just slower).
1278+
1279+ A union-typed object qualifies only when every item resolves to the same
1280+ declaration, since otherwise the value differs per item.
1281+ """
1282+ instance_type = get_proper_type (self .types .get (expr .expr ))
1283+ if isinstance (instance_type , UnionType ):
1284+ items = [get_proper_type (item ) for item in instance_type .items ]
1285+ else :
1286+ items = [instance_type ]
1287+ found : Var | None = None
1288+ for item in items :
1289+ if not isinstance (item , Instance ):
1290+ return None
1291+ var = self .class_final_var_of_instance (item , expr .name )
1292+ if var is None :
1293+ return None
1294+ if found is not None and var is not found :
1295+ return None
1296+ found = var
1297+ return found
1298+
1299+ def class_final_var_of_instance (self , instance : Instance , name : str ) -> Var | None :
1300+ """Look up a class-body Final attribute on a single instance type.
1301+
1302+ ClassIR is the authority on which attributes were compiled as class-body
1303+ Finals (mypyc.irbuild.util's is_class_body_final decides, and the answer
1304+ survives serialization), so we gate on it and only use the mypy symbol table
1305+ to find the defining class.
1306+ """
1307+ class_ir = self .mapper .type_to_ir .get (instance .type )
1308+ if class_ir is None :
1309+ return None
1310+ details = class_ir .class_final_attr_details (name )
1311+ if details is None :
1312+ return None
1313+ _ , defining_ir = details
1314+ sym = instance .type .get (name )
1315+ if sym is None or not isinstance (sym .node , Var ) or not sym .node .is_final :
1316+ return None
1317+ # mypy's MRO and mypyc's can differ (traits), so only proceed when both agree
1318+ # on which class defines the attribute; that class names the static.
1319+ if sym .node .info .fullname != defining_ir .fullname :
1320+ return None
1321+ return sym .node
1322+
12611323 def emit_load_final (
12621324 self , final_var : Var , fullname : str , name : str , native : bool , typ : Type , line : int
12631325 ) -> Value | None :
0 commit comments