@@ -110,6 +110,7 @@ private static void checkClass(SubscriptionContext ctx) {
110110 }
111111
112112 // Check each instance method
113+ String className = classDef .name ().name ();
113114 for (FunctionDef functionDef : TreeUtils .topLevelFunctionDefs (classDef )) {
114115 PythonType funcType = functionDef .name ().typeV2 ();
115116 if (!(funcType instanceof FunctionType ft ) || !ft .isInstanceMethod ()) {
@@ -118,7 +119,7 @@ private static void checkClass(SubscriptionContext ctx) {
118119 List <Parameter > params = TreeUtils .positionalParameters (functionDef );
119120 if (!params .isEmpty ()) {
120121 String selfName = params .get (0 ).name ().name ();
121- SelfAttributeAssignmentVisitor visitor = new SelfAttributeAssignmentVisitor (selfName , allowedSlots , ctx );
122+ SelfAttributeAssignmentVisitor visitor = new SelfAttributeAssignmentVisitor (selfName , allowedSlots , className , ctx );
122123 functionDef .body ().accept (visitor );
123124 }
124125 }
@@ -298,11 +299,13 @@ private static boolean processParentClass(ClassDef parentClassDef, Set<String> a
298299 private static class SelfAttributeAssignmentVisitor extends BaseTreeVisitor {
299300 private final String selfName ;
300301 private final Set <String > allowedSlots ;
302+ private final String className ;
301303 private final SubscriptionContext ctx ;
302304
303- SelfAttributeAssignmentVisitor (String selfName , Set <String > allowedSlots , SubscriptionContext ctx ) {
305+ SelfAttributeAssignmentVisitor (String selfName , Set <String > allowedSlots , String className , SubscriptionContext ctx ) {
304306 this .selfName = selfName ;
305307 this .allowedSlots = allowedSlots ;
308+ this .className = className ;
306309 this .ctx = ctx ;
307310 }
308311
@@ -346,9 +349,23 @@ private void checkQualifiedExpression(Expression expr) {
346349 return ;
347350 }
348351 String attrName = qualifiedExpr .name ().name ();
349- if (!allowedSlots .contains (attrName )) {
352+ if (!allowedSlots .contains (attrName ) && ! allowedSlots . contains ( mangledName ( attrName )) ) {
350353 ctx .addIssue (qualifiedExpr .name (), String .format (MESSAGE , attrName ));
351354 }
352355 }
356+
357+ /**
358+ * Returns the name-mangled form of a private attribute name within this class.
359+ * Python mangles names starting with two underscores (but not ending with two underscores)
360+ * by stripping leading underscores from the class name and prepending "_ClassName".
361+ * For example, "__den" in class "_Rat" becomes "_Rat__den" (not "__Rat__den").
362+ * See: https://docs.python.org/3/reference/expressions.html#atom-identifiers
363+ */
364+ private String mangledName (String attrName ) {
365+ if (attrName .startsWith ("__" ) && !attrName .endsWith ("__" )) {
366+ return "_" + className .replaceAll ("^_+" , "" ) + attrName ;
367+ }
368+ return attrName ;
369+ }
353370 }
354371}
0 commit comments