@@ -792,7 +792,7 @@ def assign(self, expr: ast.expr) -> ast.Name:
792792 """Give *expr* a name."""
793793 name = self .variable ()
794794 self .statements .append (ast .Assign ([ast .Name (name , ast .Store ())], expr ))
795- return ast .Name (name , ast .Load ())
795+ return ast .copy_location ( ast . Name (name , ast .Load ()), expr )
796796
797797 def display (self , expr : ast .expr ) -> ast .expr :
798798 """Call saferepr on the expression."""
@@ -975,7 +975,10 @@ def visit_Assert(self, assert_: ast.Assert) -> list[ast.stmt]:
975975 # Fix locations (line numbers/column offsets).
976976 for stmt in self .statements :
977977 for node in traverse_node (stmt ):
978- ast .copy_location (node , assert_ )
978+ if getattr (node , "lineno" , None ) is None :
979+ # apply the assertion location to all generated ast nodes without source location
980+ # and preserve the location of existing nodes or generated nodes with an correct location.
981+ ast .copy_location (node , assert_ )
979982 return self .statements
980983
981984 def visit_NamedExpr (self , name : ast .NamedExpr ) -> tuple [ast .NamedExpr , str ]:
@@ -1052,15 +1055,17 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> tuple[ast.Name, str]:
10521055 def visit_UnaryOp (self , unary : ast .UnaryOp ) -> tuple [ast .Name , str ]:
10531056 pattern = UNARY_MAP [unary .op .__class__ ]
10541057 operand_res , operand_expl = self .visit (unary .operand )
1055- res = self .assign (ast .UnaryOp (unary .op , operand_res ))
1058+ res = self .assign (ast .copy_location ( ast . UnaryOp (unary .op , operand_res ), unary ))
10561059 return res , pattern % (operand_expl ,)
10571060
10581061 def visit_BinOp (self , binop : ast .BinOp ) -> tuple [ast .Name , str ]:
10591062 symbol = BINOP_MAP [binop .op .__class__ ]
10601063 left_expr , left_expl = self .visit (binop .left )
10611064 right_expr , right_expl = self .visit (binop .right )
10621065 explanation = f"({ left_expl } { symbol } { right_expl } )"
1063- res = self .assign (ast .BinOp (left_expr , binop .op , right_expr ))
1066+ res = self .assign (
1067+ ast .copy_location (ast .BinOp (left_expr , binop .op , right_expr ), binop )
1068+ )
10641069 return res , explanation
10651070
10661071 def visit_Call (self , call : ast .Call ) -> tuple [ast .Name , str ]:
@@ -1089,7 +1094,7 @@ def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]:
10891094 arg_expls .append ("**" + expl )
10901095
10911096 expl = "{}({})" .format (func_expl , ", " .join (arg_expls ))
1092- new_call = ast .Call (new_func , new_args , new_kwargs )
1097+ new_call = ast .copy_location ( ast . Call (new_func , new_args , new_kwargs ), call )
10931098 res = self .assign (new_call )
10941099 res_expl = self .explanation_param (self .display (res ))
10951100 outer_expl = f"{ res_expl } \n {{{ res_expl } = { expl } \n }}"
@@ -1105,7 +1110,9 @@ def visit_Attribute(self, attr: ast.Attribute) -> tuple[ast.Name, str]:
11051110 if not isinstance (attr .ctx , ast .Load ):
11061111 return self .generic_visit (attr )
11071112 value , value_expl = self .visit (attr .value )
1108- res = self .assign (ast .Attribute (value , attr .attr , ast .Load ()))
1113+ res = self .assign (
1114+ ast .copy_location (ast .Attribute (value , attr .attr , ast .Load ()), attr )
1115+ )
11091116 res_expl = self .explanation_param (self .display (res ))
11101117 pat = "%s\n {%s = %s.%s\n }"
11111118 expl = pat % (res_expl , res_expl , value_expl , attr .attr )
@@ -1146,7 +1153,7 @@ def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]:
11461153 syms .append (ast .Constant (sym ))
11471154 expl = f"{ left_expl } { sym } { next_expl } "
11481155 expls .append (ast .Constant (expl ))
1149- res_expr = ast .Compare (left_res , [op ], [next_res ])
1156+ res_expr = ast .copy_location ( ast . Compare (left_res , [op ], [next_res ]), comp )
11501157 self .statements .append (ast .Assign ([store_names [i ]], res_expr ))
11511158 left_res , left_expl = next_res , next_expl
11521159 # Use pytest.assertion.util._reprcompare if that's available.
0 commit comments