Skip to content

Commit 78ab68a

Browse files
authored
Merge pull request RustPython#817 from skinny121/function_metadata
Add __name__, __qualname__ and __module__ to functions and classes
2 parents 2c88790 + 2164cb5 commit 78ab68a

File tree

6 files changed

+103
-17
lines changed

6 files changed

+103
-17
lines changed

tests/snippets/class.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
__name__ = "class"
2+
3+
14
class Foo:
25
def __init__(self, x):
36
assert x == 5
@@ -14,7 +17,12 @@ def square(self):
1417
assert foo.y == Foo.y
1518
assert foo.x == 5
1619
assert foo.square() == 25
17-
20+
assert Foo.__name__ == "Foo"
21+
assert Foo.__qualname__ == "Foo"
22+
assert Foo.__module__ == "class"
23+
assert Foo.square.__name__ == "square"
24+
assert Foo.square.__qualname__ == "Foo.square"
25+
assert Foo.square.__module__ == "class"
1826

1927
class Bar:
2028
""" W00t """

tests/snippets/function.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
2+
__name__ = "function"
3+
4+
15
def foo():
26
"""test"""
37
return 42
48

59
assert foo() == 42
610
assert foo.__doc__ == "test"
11+
assert foo.__name__ == "foo"
12+
assert foo.__qualname__ == "foo"
13+
assert foo.__module__ == "function"
714

815
def my_func(a,):
916
return a+2
@@ -55,3 +62,13 @@ def f5():
5562

5663
assert f5.__doc__ == 'abcw00t', f5.__doc__
5764

65+
66+
def f6():
67+
def nested():
68+
pass
69+
70+
assert nested.__name__ == "nested"
71+
assert nested.__qualname__ == "f6.<locals>.nested"
72+
73+
74+
f6()

vm/src/builtins.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::obj::objstr::{self, PyString, PyStringRef};
1818
use crate::obj::objtype::{self, PyClassRef};
1919

2020
use crate::frame::Scope;
21-
use crate::function::{Args, OptionalArg, PyFuncArgs};
21+
use crate::function::{Args, KwArgs, OptionalArg, PyFuncArgs};
2222
use crate::pyobject::{
2323
IdProtocol, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject,
2424
TypeProtocol,
@@ -783,11 +783,17 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) {
783783
});
784784
}
785785

786-
pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult {
787-
let function = args.shift();
788-
let name_arg = args.shift();
789-
let bases = args.args.clone();
790-
let mut metaclass = if let Some(metaclass) = args.get_optional_kwarg("metaclass") {
786+
pub fn builtin_build_class_(
787+
function: PyObjectRef,
788+
qualified_name: PyStringRef,
789+
bases: Args<PyClassRef>,
790+
mut kwargs: KwArgs,
791+
vm: &VirtualMachine,
792+
) -> PyResult {
793+
let name = qualified_name.value.split('.').next_back().unwrap();
794+
let name_obj = vm.new_str(name.to_string());
795+
796+
let mut metaclass = if let Some(metaclass) = kwargs.pop_kwarg("metaclass") {
791797
PyClassRef::try_from_object(vm, metaclass)?
792798
} else {
793799
vm.get_type()
@@ -801,21 +807,25 @@ pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResu
801807
}
802808
}
803809

804-
let bases = vm.context().new_tuple(bases);
810+
let bases = bases.into_tuple(vm);
805811

806812
// Prepare uses full __getattribute__ resolution chain.
807813
let prepare = vm.get_attribute(metaclass.clone().into_object(), "__prepare__")?;
808-
let namespace = vm.invoke(prepare, vec![name_arg.clone(), bases.clone()])?;
814+
let namespace = vm.invoke(prepare, vec![name_obj.clone(), bases.clone()])?;
809815

810816
let namespace: PyDictRef = TryFromObject::try_from_object(vm, namespace)?;
811817

812818
let cells = vm.ctx.new_dict();
813819

814820
vm.invoke_with_locals(function, cells.clone(), namespace.clone())?;
821+
822+
namespace.set_item("__name__", name_obj.clone(), vm)?;
823+
namespace.set_item("__qualname__", qualified_name.into_object(), vm)?;
824+
815825
let class = vm.call_method(
816826
metaclass.as_object(),
817827
"__call__",
818-
vec![name_arg, bases, namespace.into_object()],
828+
vec![name_obj, bases, namespace.into_object()],
819829
)?;
820830
cells.set_item("__class__", class.clone(), vm)?;
821831
Ok(class)

vm/src/compile.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct Compiler {
1919
nxt_label: usize,
2020
source_path: Option<String>,
2121
current_source_location: ast::Location,
22+
current_qualified_path: Option<String>,
2223
in_loop: bool,
2324
in_function_def: bool,
2425
}
@@ -75,6 +76,7 @@ impl Compiler {
7576
nxt_label: 0,
7677
source_path: None,
7778
current_source_location: ast::Location::default(),
79+
current_qualified_path: None,
7880
in_loop: false,
7981
in_function_def: false,
8082
}
@@ -610,6 +612,10 @@ impl Compiler {
610612
self.in_loop = false;
611613
self.in_function_def = true;
612614

615+
let old_qualified_path = self.current_qualified_path.clone();
616+
let qualified_name = self.create_qualified_name(name, "");
617+
self.current_qualified_path = Some(self.create_qualified_name(name, ".<locals>"));
618+
613619
self.prepare_decorators(decorator_list)?;
614620

615621
let mut flags = self.enter_function(name, args)?;
@@ -668,7 +674,7 @@ impl Compiler {
668674
});
669675
self.emit(Instruction::LoadConst {
670676
value: bytecode::Constant::String {
671-
value: name.to_string(),
677+
value: qualified_name,
672678
},
673679
});
674680

@@ -681,6 +687,7 @@ impl Compiler {
681687
name: name.to_string(),
682688
});
683689

690+
self.current_qualified_path = old_qualified_path;
684691
self.in_loop = was_in_loop;
685692
self.in_function_def = was_in_function_def;
686693
Ok(())
@@ -696,6 +703,11 @@ impl Compiler {
696703
) -> Result<(), CompileError> {
697704
let was_in_loop = self.in_loop;
698705
self.in_loop = false;
706+
707+
let old_qualified_path = self.current_qualified_path.clone();
708+
let qualified_name = self.create_qualified_name(name, "");
709+
self.current_qualified_path = Some(qualified_name.clone());
710+
699711
self.prepare_decorators(decorator_list)?;
700712
self.emit(Instruction::LoadBuildClass);
701713
let line_number = self.get_source_line_number();
@@ -711,6 +723,12 @@ impl Compiler {
711723

712724
let (new_body, doc_str) = get_doc(body);
713725

726+
self.emit(Instruction::LoadName {
727+
name: "__name__".to_string(),
728+
});
729+
self.emit(Instruction::StoreName {
730+
name: "__module__".to_string(),
731+
});
714732
self.compile_statements(new_body)?;
715733
self.emit(Instruction::LoadConst {
716734
value: bytecode::Constant::None,
@@ -737,7 +755,7 @@ impl Compiler {
737755

738756
self.emit(Instruction::LoadConst {
739757
value: bytecode::Constant::String {
740-
value: name.to_string(),
758+
value: qualified_name,
741759
},
742760
});
743761

@@ -779,6 +797,7 @@ impl Compiler {
779797
self.emit(Instruction::StoreName {
780798
name: name.to_string(),
781799
});
800+
self.current_qualified_path = old_qualified_path;
782801
self.in_loop = was_in_loop;
783802
Ok(())
784803
}
@@ -1604,6 +1623,14 @@ impl Compiler {
16041623
self.current_source_location.get_row()
16051624
}
16061625

1626+
fn create_qualified_name(&self, name: &str, suffix: &str) -> String {
1627+
if let Some(ref qualified_path) = self.current_qualified_path {
1628+
format!("{}.{}{}", qualified_path, name, suffix)
1629+
} else {
1630+
format!("{}{}", name, suffix)
1631+
}
1632+
}
1633+
16071634
fn mark_generator(&mut self) {
16081635
self.current_code_object().is_generator = true;
16091636
}

vm/src/frame.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ use crate::builtins;
1010
use crate::bytecode;
1111
use crate::function::PyFuncArgs;
1212
use crate::obj::objbool;
13-
use crate::obj::objbuiltinfunc::PyBuiltinFunction;
1413
use crate::obj::objcode::PyCodeRef;
1514
use crate::obj::objdict::{PyDict, PyDictRef};
1615
use crate::obj::objint::PyInt;
1716
use crate::obj::objiter;
1817
use crate::obj::objlist;
1918
use crate::obj::objslice::PySlice;
2019
use crate::obj::objstr;
20+
use crate::obj::objstr::PyString;
2121
use crate::obj::objtuple::PyTuple;
2222
use crate::obj::objtype;
2323
use crate::obj::objtype::PyClassRef;
@@ -577,7 +577,10 @@ impl Frame {
577577
}
578578
}
579579
bytecode::Instruction::MakeFunction { flags } => {
580-
let _qualified_name = self.pop_value();
580+
let qualified_name = self
581+
.pop_value()
582+
.downcast::<PyString>()
583+
.expect("qualified name to be a string");
581584
let code_obj = self
582585
.pop_value()
583586
.downcast()
@@ -617,6 +620,15 @@ impl Frame {
617620
.ctx
618621
.new_function(code_obj, scope, defaults, kw_only_defaults);
619622

623+
let name = qualified_name.value.split('.').next_back().unwrap();
624+
vm.set_attr(&obj, "__name__", vm.new_str(name.to_string()))?;
625+
vm.set_attr(&obj, "__qualname__", qualified_name)?;
626+
let module = self
627+
.scope
628+
.globals
629+
.get_item_option("__name__", vm)?
630+
.unwrap_or_else(|| vm.get_none());
631+
vm.set_attr(&obj, "__module__", module)?;
620632
vm.set_attr(&obj, "__annotations__", annotations)?;
621633

622634
self.push_value(obj);
@@ -737,9 +749,7 @@ impl Frame {
737749
Ok(None)
738750
}
739751
bytecode::Instruction::LoadBuildClass => {
740-
let rustfunc =
741-
PyBuiltinFunction::new(Box::new(builtins::builtin_build_class_)).into_ref(vm);
742-
self.push_value(rustfunc.into_object());
752+
self.push_value(vm.ctx.new_rustfunc(builtins::builtin_build_class_));
743753
Ok(None)
744754
}
745755
bytecode::Instruction::UnpackSequence { size } => {

vm/src/function.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,12 @@ pub trait FromArgs: Sized {
244244
/// an appropriate FromArgs implementation must be created.
245245
pub struct KwArgs<T = PyObjectRef>(HashMap<String, T>);
246246

247+
impl<T> KwArgs<T> {
248+
pub fn pop_kwarg(&mut self, name: &str) -> Option<T> {
249+
self.0.remove(name)
250+
}
251+
}
252+
247253
impl<T> FromArgs for KwArgs<T>
248254
where
249255
T: TryFromObject,
@@ -274,8 +280,16 @@ impl<T> IntoIterator for KwArgs<T> {
274280
///
275281
/// `Args` optionally accepts a generic type parameter to allow type checks
276282
/// or conversions of each argument.
283+
#[derive(Clone)]
277284
pub struct Args<T = PyObjectRef>(Vec<T>);
278285

286+
impl<T: PyValue> Args<PyRef<T>> {
287+
pub fn into_tuple(self, vm: &VirtualMachine) -> PyObjectRef {
288+
vm.ctx
289+
.new_tuple(self.0.into_iter().map(|obj| obj.into_object()).collect())
290+
}
291+
}
292+
279293
impl<T> FromArgs for Args<T>
280294
where
281295
T: TryFromObject,

0 commit comments

Comments
 (0)