[mypyc] Use safe borrowing for augmented assignment under free-threading

Work on mypyc/mypyc#1203.
diff --git a/mypyc/irbuild/ast_helpers.py b/mypyc/irbuild/ast_helpers.py
index 8fff4b1..57b5e15 100644
--- a/mypyc/irbuild/ast_helpers.py
+++ b/mypyc/irbuild/ast_helpers.py
@@ -20,8 +20,9 @@
     UnaryExpr,
     Var,
 )
+from mypyc.common import IS_FREE_THREADED
 from mypyc.ir.ops import BasicBlock
-from mypyc.ir.rtypes import is_fixed_width_rtype, is_tagged
+from mypyc.ir.rtypes import RVec, is_fixed_width_rtype, is_tagged
 from mypyc.irbuild.builder import IRBuilder
 from mypyc.irbuild.constant_fold import constant_fold_expr
 
@@ -119,5 +120,14 @@
             # Local variable reference can be borrowed
             return True
     if isinstance(expr, MemberExpr) and self.is_native_attr_ref(expr):
-        return True
+        # Borrowing a native attribute read is unsafe on free-threaded builds, since another
+        # thread could concurrently reassign the attribute and free the old value (this is the
+        # same rule transform_member_expr applies). We still borrow in two cases:
+        #  - Native Final attributes are read-only at runtime, so they can never be reassigned.
+        #  - Vec-typed attributes require manual synchronization, so we borrow them liberally.
+        return (
+            not IS_FREE_THREADED
+            or isinstance(self.node_type(expr), RVec)
+            or self.is_final_native_attr_ref(expr)
+        )
     return False
diff --git a/mypyc/test-data/irbuild-classes.test b/mypyc/test-data/irbuild-classes.test
index 8eaa63e..e9c2d92 100644
--- a/mypyc/test-data/irbuild-classes.test
+++ b/mypyc/test-data/irbuild-classes.test
@@ -156,7 +156,7 @@
     self.y = 60
     return 1
 
-[case testAttrLvalue]
+[case testAttrLvalue_withgil]
 class O(object):
     def __init__(self) -> None:
         self.x = 1
@@ -180,6 +180,34 @@
     o.x = r1; r2 = is_error
     return o
 
+[case testAttrLvalue_nogil]
+# On free-threaded builds an operator assignment such as 'o.x += 1' must not borrow
+# the native attribute read: a concurrent store could free the old value between the
+# borrowed load and its use. Contrast testAttrLvalue_withgil, where the read is
+# borrowed. is_borrow_friendly_expr applies the same rule as transform_member_expr.
+class O(object):
+    def __init__(self) -> None:
+        self.x = 1
+
+def increment(o: O) -> O:
+    o.x += 1
+    return o
+[out]
+def O.__init__(self):
+    self :: __main__.O
+L0:
+    self.x = 2
+    return 1
+def increment(o):
+    o :: __main__.O
+    r0, r1 :: int
+    r2 :: bool
+L0:
+    r0 = o.x
+    r1 = CPyTagged_Add(r0, 2)
+    o.x = r1; r2 = is_error
+    return o
+
 [case testSubclass_withgil_toplevel]
 from typing import TypeVar, Generic
 from mypy_extensions import trait