99//! in scope rather than to a member of `x`. An unapplied `x.fn` becomes a
1010//! `functools.partial`, matching how a bound method would have carried the
1111//! receiver
12- //! - a bare name in a trailing lambda block that resolves to a member of the
13- //! block's receiver → `it.<name>`, where `it` is the block's implicit
14- //! parameter ( the receiver itself )
12+ //! - a bare name in a trailing lambda block that resolves through the block's
13+ //! receiver → the block's receiver parameter, either on its own (`self`) or
14+ //! as the object a member is read off (`<receiver>.<name>` )
1515//!
1616//! Both are narrow edits nested inside the trailing-lambda template's `Src`
1717//! spans, so they compose with the block lowering.
1818//!
1919//! [`callable`]: super::callable
2020
21- use ruff_python_ast:: visitor:: { Visitor , walk_expr, walk_stmt } ;
22- use ruff_python_ast:: { Expr , ExprAttribute , ExprContext , Stmt } ;
21+ use ruff_python_ast:: visitor:: { Visitor , walk_expr} ;
22+ use ruff_python_ast:: { Expr , ExprAttribute , Stmt } ;
2323use ruff_text_size:: { Ranged , TextRange } ;
24+ use ty_python_semantic:: ImplicitReceiverReference ;
2425
2526use super :: ast_driver:: { Fragment , PassContext , TypeAwarePass } ;
2627use super :: extension:: { arguments_span, spine_has_optional} ;
28+ use super :: trailing_lambda:: RECEIVER_PARAMETER ;
2729use crate :: type_info:: TypeInfo ;
2830
2931struct ImplicitReceiverLower < ' a > {
@@ -33,62 +35,10 @@ struct ImplicitReceiverLower<'a> {
3335 /// attribute ranges already rewritten as part of an enclosing call, so the
3436 /// bare-access arm doesn't rewrite them a second time
3537 handled : Vec < TextRange > ,
36- /// the enclosing trailing lambda blocks, innermost last, and whether each
37- /// rebinds `it` — the name the receiver members are read from
38- blocks : Vec < Block > ,
3938 needs_functools : bool ,
4039}
4140
42- /// An enclosing trailing lambda block, for the `it`-rebinding check
43- struct Block {
44- rebinds_it : bool ,
45- reported : bool ,
46- }
47-
48- /// Whether a block assigns `it` anywhere in its body. The lowering reads the
49- /// receiver's members off that parameter, so a rebinding would silently redirect
50- /// them to the new value. Nested scopes count too: this only decides whether to
51- /// reject, and over-rejecting is the safe direction.
52- fn rebinds_it ( body : & [ Stmt ] ) -> bool {
53- struct RebindsIt {
54- found : bool ,
55- }
56-
57- impl < ' ast > Visitor < ' ast > for RebindsIt {
58- fn visit_expr ( & mut self , expr : & ' ast Expr ) {
59- if let Expr :: Name ( name) = expr
60- && name. id . as_str ( ) == "it"
61- && matches ! ( name. ctx, ExprContext :: Store | ExprContext :: Del )
62- {
63- self . found = true ;
64- }
65- walk_expr ( self , expr) ;
66- }
67- }
68-
69- let mut visitor = RebindsIt { found : false } ;
70- for stmt in body {
71- visitor. visit_stmt ( stmt) ;
72- }
73- visitor. found
74- }
75-
7641impl < ' ast > Visitor < ' ast > for ImplicitReceiverLower < ' _ > {
77- fn visit_stmt ( & mut self , stmt : & ' ast Stmt ) {
78- if let Stmt :: FunctionDef ( function) = stmt
79- && function. is_trailing_lambda
80- {
81- self . blocks . push ( Block {
82- rebinds_it : rebinds_it ( & function. body ) ,
83- reported : false ,
84- } ) ;
85- walk_stmt ( self , stmt) ;
86- self . blocks . pop ( ) ;
87- return ;
88- }
89- walk_stmt ( self , stmt) ;
90- }
91-
9242 fn visit_expr ( & mut self , expr : & ' ast Expr ) {
9343 match expr {
9444 // `x.fn(a)` → `fn(x, a)`
@@ -127,15 +77,20 @@ impl<'ast> Visitor<'ast> for ImplicitReceiverLower<'_> {
12777 }
12878 }
12979 }
130- // a receiver member used unqualified inside a trailing lambda block
80+ // the receiver of a trailing lambda block, or one of its members,
81+ // used unqualified inside the block
13182 Expr :: Name ( name) => {
132- if name. ctx . is_load ( ) && self . types . is_implicit_receiver_name ( name) {
133- if self . block_rebinds_it ( ) {
134- self . report_rebound_it ( ) ;
135- } else {
136- self . edits
137- . push ( ( name. range ( ) , vec ! [ Fragment :: Lit ( format!( "it.{}" , name. id) ) ] ) ) ;
138- }
83+ if name. ctx . is_load ( )
84+ && let Some ( reference) = self . types . implicit_receiver_name ( name)
85+ {
86+ let lowered = match reference {
87+ ImplicitReceiverReference :: Receiver => RECEIVER_PARAMETER . to_owned ( ) ,
88+ ImplicitReceiverReference :: Member => {
89+ format ! ( "{RECEIVER_PARAMETER}.{}" , name. id)
90+ }
91+ } ;
92+ self . edits
93+ . push ( ( name. range ( ) , vec ! [ Fragment :: Lit ( lowered) ] ) ) ;
13994 }
14095 }
14196 _ => { }
@@ -159,28 +114,6 @@ impl ImplicitReceiverLower<'_> {
159114 fragments. push ( Fragment :: Lit ( ")" . to_owned ( ) ) ) ;
160115 self . edits . push ( ( call. range ( ) , fragments) ) ;
161116 }
162-
163- /// whether the innermost enclosing block rebinds `it`, which the member
164- /// rewrite reads the receiver from
165- fn block_rebinds_it ( & self ) -> bool {
166- self . blocks . last ( ) . is_some_and ( |block| block. rebinds_it )
167- }
168-
169- /// report the rebinding once per block, however many members it uses
170- fn report_rebound_it ( & mut self ) {
171- let Some ( block) = self . blocks . last_mut ( ) else {
172- return ;
173- } ;
174- if block. reported {
175- return ;
176- }
177- block. reported = true ;
178- self . errors . push (
179- "a trailing lambda block that rebinds `it` cannot use its receiver's members \
180- unqualified yet — the lowering reads them from `it`"
181- . to_owned ( ) ,
182- ) ;
183- }
184117}
185118
186119/// whether the access is a link of a `?.` chain, which the receiver rewrite
@@ -202,7 +135,6 @@ impl TypeAwarePass for ImplicitReceiverPass {
202135 edits : Vec :: new ( ) ,
203136 handled : Vec :: new ( ) ,
204137 errors : Vec :: new ( ) ,
205- blocks : Vec :: new ( ) ,
206138 needs_functools : false ,
207139 } ;
208140 for stmt in stmts {
@@ -270,15 +202,51 @@ mod tests {
270202 }
271203
272204 #[ test]
273- fn trailing_block_members_bind_to_it ( ) {
205+ fn trailing_block_members_bind_to_the_receiver_parameter ( ) {
274206 let out = check ( indoc ! { "
275207 def f(fn: int.() -> None) -> None:
276208 fn(1)
277209
278210 f:
279211 print(imag)
280212 " } ) ;
281- assert ! ( out. contains( "print(it.imag)" ) , "got:\n {out}" ) ;
213+ assert ! ( out. contains( "print(_by_self.imag)" ) , "got:\n {out}" ) ;
214+ assert ! (
215+ out. contains( "def _trailing_lambda_0(_by_self=None, it=None):" ) ,
216+ "got:\n {out}"
217+ ) ;
218+ }
219+
220+ #[ test]
221+ fn a_trailing_block_spells_its_receiver_self ( ) {
222+ let out = check ( indoc ! { "
223+ def f(fn: str.(int) -> None) -> None:
224+ fn(\" a\" , 1)
225+
226+ f:
227+ print(upper(), it, self)
228+ " } ) ;
229+ assert ! (
230+ out. contains( "print(_by_self.upper(), it, _by_self)" ) ,
231+ "got:\n {out}"
232+ ) ;
233+ }
234+
235+ #[ test]
236+ fn an_enclosing_self_keeps_its_meaning ( ) {
237+ // the receiver is the *last* fallback, so a method's own `self` still
238+ // wins — and the block's receiver parameter is a name the source cannot
239+ // spell, so it cannot be shadowed either
240+ let out = check ( indoc ! { "
241+ def f(fn: str.() -> None) -> None:
242+ fn(\" a\" )
243+
244+ class C:
245+ def m(self) -> None:
246+ f:
247+ print(self, upper())
248+ " } ) ;
249+ assert ! ( out. contains( "print(self, _by_self.upper())" ) , "got:\n {out}" ) ;
282250 }
283251
284252 #[ test]
@@ -295,20 +263,18 @@ mod tests {
295263 }
296264
297265 #[ test]
298- fn rebinding_it_is_rejected ( ) {
299- let error = transpile (
300- indoc ! { "
301- def f(fn: str.() -> None) -> None:
302- fn(\" abc\" )
266+ fn a_block_local_shadows_the_receiver_spelling ( ) {
267+ // a block that binds `self` itself keeps that binding — the receiver's
268+ // members are read off the block's own parameter, so they are unaffected
269+ let out = check ( indoc ! { "
270+ def f(fn: str.() -> None) -> None:
271+ fn(\" abc\" )
303272
304- f:
305- it = 5
306- print(upper())
307- " } ,
308- & Config :: test_default ( ) ,
309- )
310- . expect_err ( "a block that rebinds `it` should be rejected" ) ;
311- assert ! ( error. contains( "rebinds `it`" ) , "got:\n {error}" ) ;
273+ f:
274+ self = 5
275+ print(self, upper())
276+ " } ) ;
277+ assert ! ( out. contains( "print(self, _by_self.upper())" ) , "got:\n {out}" ) ;
312278 }
313279
314280 #[ test]
0 commit comments