Skip to content

Commit 31b9efe

Browse files
committed
Fix __qualname__ of compiler-generated __annotate__ functions
Assisted-by: Codex:5.6-sol
1 parent 81df1ff commit 31b9efe

2 files changed

Lines changed: 46 additions & 19 deletions

File tree

Lib/test/test_type_annotations.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,6 @@ def test_complex_comprehension_inlining_exec(self):
843843
lamb = list(genexp)[0]
844844
self.assertEqual(lamb(), 42)
845845

846-
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: '__annotate__' != 'f.__annotate__'
847846
def test_annotate_qualname(self):
848847
code = """
849848
def f() -> None:

crates/codegen/src/compile.rs

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2175,7 +2175,7 @@ impl<'warnings> Compiler<'warnings> {
21752175
/// On success, returns the saved CompileContext to pass to exit_annotation_scope.
21762176
fn enter_annotation_scope(
21772177
&mut self,
2178-
_func_name: &str,
2178+
func_name: &str,
21792179
loc: TextRange,
21802180
) -> CompileResult<Option<CompileContext>> {
21812181
if !self.push_annotation_symbol_table() {
@@ -2200,6 +2200,12 @@ impl<'warnings> Compiler<'warnings> {
22002200
lineno.to_u32(),
22012201
)?;
22022202

2203+
// enter_scope() qualified the scope by the enclosing scope only; redo it
2204+
// now that the annotated function is known. Only signature annotations
2205+
// get this treatment - deferred class and module annotations are
2206+
// compiled inside the scope they belong to and are already qualified.
2207+
self.set_annotation_qualname(func_name);
2208+
22032209
// Keep the internal ".format" name; exit_annotation_scope()
22042210
// renames it to "format" on the final code object.
22052211
self.current_code_info()
@@ -2605,11 +2611,24 @@ impl<'warnings> Compiler<'warnings> {
26052611
/// Set the qualified name for the current code object
26062612
// = compiler_set_qualname
26072613
fn set_qualname(&mut self) -> String {
2608-
let qualname = self.make_qualname();
2614+
self.set_qualname_for_function(None)
2615+
}
2616+
2617+
/// Set the qualname of an annotation scope, qualified by the function whose
2618+
/// signature it annotates. CPython records that name on the annotation
2619+
/// block's symbol table entry (`ste_function_name`) and folds it into the
2620+
/// qualname, so `f`'s annotation scope is named `f.__annotate__`.
2621+
fn set_annotation_qualname(&mut self, function_name: &str) {
2622+
self.set_qualname_for_function(Some(function_name));
2623+
}
2624+
2625+
fn set_qualname_for_function(&mut self, function_name: Option<&str>) -> String {
2626+
let qualname = self.make_qualname(function_name);
26092627
self.current_code_info().metadata.qualname = Some(qualname.clone());
26102628
qualname
26112629
}
2612-
fn make_qualname(&mut self) -> String {
2630+
2631+
fn make_qualname(&mut self, function_name: Option<&str>) -> String {
26132632
let stack_size = self.code_stack.len();
26142633
assert!(stack_size >= 1);
26152634

@@ -2693,10 +2712,10 @@ impl<'warnings> Compiler<'warnings> {
26932712
}
26942713
}
26952714

2696-
// Build the qualified name
2697-
if force_global {
2715+
// Build the prefix the current name is qualified by, if any
2716+
let base = if force_global {
26982717
// For global symbols, qualname is just the name
2699-
current_obj_name
2718+
None
27002719
} else {
27012720
// Check parent scope type
27022721
let parent_obj_name = &parent.metadata.name;
@@ -2709,23 +2728,32 @@ impl<'warnings> Compiler<'warnings> {
27092728
)
27102729
);
27112730

2731+
// Use parent's qualname if available, otherwise use parent_obj_name
2732+
let parent_qualname = parent.metadata.qualname.as_ref().unwrap_or(parent_obj_name);
2733+
27122734
if is_function_parent {
27132735
// For functions, append .<locals> to parent qualname
2714-
// Use parent's qualname if available, otherwise use parent_obj_name
2715-
let parent_qualname = parent.metadata.qualname.as_ref().unwrap_or(parent_obj_name);
2716-
format!("{parent_qualname}.<locals>.{current_obj_name}")
2736+
Some(format!("{parent_qualname}.<locals>"))
2737+
} else if parent_qualname == "<module>" {
2738+
// Module level, nothing to qualify by
2739+
None
27172740
} else {
27182741
// For classes and other scopes, use parent's qualname directly
2719-
// Use parent's qualname if available, otherwise use parent_obj_name
2720-
let parent_qualname = parent.metadata.qualname.as_ref().unwrap_or(parent_obj_name);
2721-
if parent_qualname == "<module>" {
2722-
// Module level, just use the name
2723-
current_obj_name
2724-
} else {
2725-
// Concatenate parent qualname with current name
2726-
format!("{parent_qualname}.{current_obj_name}")
2727-
}
2742+
Some(parent_qualname.clone())
27282743
}
2744+
};
2745+
2746+
// An annotation scope is compiled in the scope enclosing the function it
2747+
// annotates, so the function itself is missing from the prefix above.
2748+
let base = match (base, function_name) {
2749+
(Some(base), Some(function_name)) => Some(format!("{base}.{function_name}")),
2750+
(None, Some(function_name)) => Some(function_name.to_owned()),
2751+
(base, None) => base,
2752+
};
2753+
2754+
match base {
2755+
Some(base) => format!("{base}.{current_obj_name}"),
2756+
None => current_obj_name,
27292757
}
27302758
}
27312759

0 commit comments

Comments
 (0)