///|
/// Attribute lookup, descriptors, and attribute mutation.
///|
fn lookup_class_attr(
klass : ClassValue,
name : String,
) -> Result[Value?, RuntimeError] {
let mro = match class_mro(klass) {
Ok(value) => value
Err(err) => return Err(err)
}
for cls in mro {
let direct = get_named_value(cls.dict, name)
if direct is Some(value) {
return Ok(Some(value))
}
if name == "__repr__" && is_builtin_class_name(cls.name) {
// Built-in container types provide a synthetic `X.__repr__` descriptor.
// Make it visible through MRO lookup so subclasses inherit it (e.g. pprint
// dispatch keyed by `type(x).__repr__`).
return Ok(
Some(
Value::Function(FunctionValue::{
name: cls.name + ".__repr__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
)
}
}
Ok(None)
}
///|
fn class_has_base_name(klass : ClassValue, name : String) -> Bool {
match class_mro(klass) {
Ok(mro) => {
for cls in mro {
if cls.name == name {
return true
}
}
false
}
Err(_) => false
}
}
///|
fn truthy_from_value_with_env(
value : Value,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Bool, RuntimeError] {
match value {
Value::Instance(_) => {
// __bool__()
// Special-method lookup should not invoke instance-level __getattr__.
match get_attr_raw_from_value(value, "__bool__", globals, builtins, io) {
Ok(callable) => {
let result = match
call_callable_with_env(callable, [], [], globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
match result {
Value::Bool(v) => return Ok(v)
_ =>
return Err(
make_runtime_error(
RuntimeErrorKind::Type,
"__bool__ should return bool".to_string(),
),
)
}
}
Err(err) =>
match err.kind {
RuntimeErrorKind::Attribute => ()
_ => return Err(err)
}
}
// __len__()
match get_attr_raw_from_value(value, "__len__", globals, builtins, io) {
Ok(callable) => {
let result = match
call_callable_with_env(callable, [], [], globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
let n = match result {
Value::Int(v) => v
Value::Bool(v) => @bigint.BigInt::from_int(if v { 1 } else { 0 })
_ =>
return Err(
make_runtime_error(
RuntimeErrorKind::Type,
"__len__() should return an int".to_string(),
),
)
}
if n < 0N {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"ValueError: __len__() should return >= 0".to_string(),
),
)
}
return Ok(n != 0N)
}
Err(err) =>
match err.kind {
RuntimeErrorKind::Attribute =>
// Built-in container subclasses (e.g. `class X(list)`) inherit
// length semantics even though the intrinsic `__len__` descriptor
// is not materialized in the class dict. Fall back to the
// internal storage when present.
match value {
Value::Instance(inst) => {
match get_named_value(inst.dict, list_storage_name) {
Some(Value::List(values)) => return Ok(values.length() != 0)
_ => ()
}
match get_named_value(inst.dict, dict_storage_name) {
Some(Value::Dict(values)) => return Ok(values.length() != 0)
_ => ()
}
match get_named_value(inst.dict, set_storage_name) {
Some(Value::Set(values)) => return Ok(values.length() != 0)
_ => ()
}
}
_ => ()
}
_ => return Err(err)
}
}
Ok(true)
}
_ => Ok(bool_from_value(value))
}
}
///|
fn get_attr_raw_from_value(
target : Value,
attr : String,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Value, RuntimeError] {
if attr == "__call__" {
match target {
Value::Function(_) | Value::BoundMethod(_) => return Ok(target)
_ => ()
}
}
fn apply_descriptor(
class_attr : Value,
obj : Value,
owner : Value,
) -> Result[Value?, RuntimeError] {
// Built-in descriptor: property(fget, fset, fdel, doc)
match class_attr {
Value::Instance(inst) =>
if class_has_base_name(inst.class, "property") {
match get_named_value(inst.dict, "fget") {
Some(fget) =>
match obj {
Value::None => return Ok(Some(class_attr))
_ => {
if fget is Value::None {
let owner_name = match owner {
Value::Class(klass) =>
match get_named_value(klass.dict, "__qualname__") {
Some(Value::Str(text)) => text
_ => klass.name
}
_ => type_name_from_value(owner)
}
let property_name = match class_attr {
Value::Instance(prop_inst) =>
match get_named_value(prop_inst.dict, "__name__") {
Some(Value::Str(text)) => Some(text)
_ => None
}
_ => None
}
let msg = match property_name {
Some(prop) =>
"property '" +
prop +
"' of '" +
owner_name +
"' object has no getter"
None =>
"property of '" + owner_name + "' object has no getter"
}
return Err(
make_runtime_error(RuntimeErrorKind::Attribute, msg),
)
}
return match
call_callable_with_env(
fget,
[obj],
[],
globals,
builtins,
io,
) {
Ok(v) => Ok(Some(v))
Err(err) => Err(err)
}
}
}
None => return Ok(None)
}
}
_ => ()
}
// Built-in descriptor: staticmethod(func)
match class_attr {
Value::Instance(inst) =>
if class_has_base_name(inst.class, "staticmethod") {
match get_named_value(inst.dict, "func") {
Some(func) => return Ok(Some(func))
None => return Ok(None)
}
}
_ => ()
}
// Built-in descriptor: classmethod(func)
match class_attr {
Value::Instance(inst) =>
if class_has_base_name(inst.class, "classmethod") {
match get_named_value(inst.dict, "func") {
Some(func_value) =>
match func_value {
Value::Function(func) =>
// Call with the owner class as the implicit first argument.
return Ok(
Some(
Value::BoundMethod(BoundMethodValue::{
function: func,
self: owner,
}),
),
)
_ =>
// CPython allows wrapping other callables (e.g.
// `__class_getitem__ = classmethod(GenericAlias)` in `os`).
// We can't represent a generic "bound callable" yet, so
// return the underlying callable and let specific call sites
// (like subscription) provide the explicit `cls` argument.
return Ok(Some(func_value))
}
None => return Ok(None)
}
}
_ => ()
}
let desc_class = match class_attr {
Value::Instance(d) => Some(d.class)
Value::Class(d) => Some(d)
_ => None
}
if desc_class is None {
return Ok(None)
}
let desc_class = desc_class.unwrap()
let get_opt = match lookup_class_attr(desc_class, "__get__") {
Ok(v) => v
Err(err) => return Err(err)
}
match get_opt {
Some(Value::Function(get_fn)) => {
let args : Array[Value] = [class_attr, obj, owner]
match
call_callable_with_env(
Value::Function(get_fn),
args,
[],
globals,
builtins,
io,
) {
Ok(v) => Ok(Some(v))
Err(err) => Err(err)
}
}
_ => Ok(None)
}
}
if attr == "__class__" {
match target {
Value::Instance(inst) => return Ok(Value::Class(inst.class))
Value::Class(klass) =>
match get_named_value(klass.dict, "__mpython_metaclass__") {
Some(Value::Class(meta)) => return Ok(Value::Class(meta))
_ => ()
}
_ =>
return Ok(
Value::Class(
builtin_class_from_name(type_name_from_value(target), builtins),
),
)
}
}
match target {
Value::None =>
if attr == "__doc__" {
Ok(Value::Str("None"))
} else {
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'NoneType' object has no attribute '" + attr + "'",
),
)
}
Value::Instance(inst) => {
if inst.class.name == "module" && attr == "__dict__" {
// `module.__dict__` must be a live mapping so stdlib helpers like
// `sys.modules[mod].__dict__.update(...)` can mutate module globals.
return Ok(make_module_globals_dict_instance(inst.dict))
}
if inst.class.name == "code" && attr == "co_positions" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "code.co_positions",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if inst.class.name == "slice" && attr == "indices" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "slice.indices",
params: ["self", "length"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if inst.class.name == "file" && attr == "closed" {
match get_named_value(inst.dict, "__closed__") {
Some(Value::Bool(v)) => return Ok(Value::Bool(v))
_ => return Ok(Value::Bool(false))
}
}
if attr == "__dict__" {
// `instance.__dict__` must be a live mapping so writes like
// `obj.__dict__["x"] = 1` reflect as instance attributes.
return Ok(make_module_globals_dict_instance(inst.dict))
}
if inst.class.name == "TypeAlias" && attr == "__value__" {
match get_named_value(inst.dict, "__value_func__") {
Some(value_func) =>
return call_callable_with_env(
value_func,
[],
[],
globals,
builtins,
io,
)
None => ()
}
}
let direct = get_named_value(inst.dict, attr)
if direct is Some(value) {
return Ok(value)
}
// Delegate common set/frozenset methods to the underlying set storage so
// `frozenset(...).__contains__` and set subclasses behave reasonably.
match get_named_value(inst.dict, set_storage_name) {
Some(Value::Set(values)) =>
match
get_attr_raw_from_value(
Value::Set(values),
attr,
globals,
builtins,
io,
) {
Ok(v) => return Ok(v)
Err(err) =>
match err.kind {
RuntimeErrorKind::Attribute => ()
_ => return Err(err)
}
}
_ => ()
}
if inst.class.name == "memoryview" {
if attr == "tobytes" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "memoryview.tobytes",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "cast" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "memoryview.cast",
params: ["self", "format", "shape"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
}
if inst.class.name == "module" {
// PEP 562: if a module defines `__getattr__`, use it to resolve missing
// attributes (e.g. unittest's lazy IsolatedAsyncioTestCase import).
match get_named_value(inst.dict, "__getattr__") {
Some(getter) =>
match
call_callable_with_env(
getter,
[Value::Str(attr)],
[],
inst.dict,
builtins,
io,
) {
Ok(v) => return Ok(v)
Err(err) => return Err(err)
}
None => ()
}
}
fn abstract_flag_from_value(value : Value) -> Result[Bool, RuntimeError] {
match value {
Value::Function(func) => {
let pairs = function_attr_dict(func)
let idx = match
dict_find_index(pairs, Value::Str("__isabstractmethod__")) {
Ok(v) => v
Err(_) => None
}
match idx {
Some(i) =>
truthy_from_value_with_env(pairs[i].1, globals, builtins, io)
None => Ok(false)
}
}
Value::Instance(inner) =>
if inner.class.name == "classmethod" ||
inner.class.name == "staticmethod" {
match get_named_value(inner.dict, "func") {
Some(func_value) => abstract_flag_from_value(func_value)
None => Ok(false)
}
} else {
Ok(false)
}
_ => Ok(false)
}
}
if class_has_base_name(inst.class, "property") && attr == "__doc__" {
match get_named_value(inst.dict, "doc") {
Some(Value::None) | None =>
match get_named_value(inst.dict, "fget") {
Some(fget) =>
match
get_attr_from_value(fget, "__doc__", globals, builtins, io) {
Ok(v) => return Ok(v)
Err(err) =>
match err.kind {
RuntimeErrorKind::Attribute => return Ok(Value::None)
_ => return Err(err)
}
}
None => return Ok(Value::None)
}
Some(value) => return Ok(value)
}
}
if attr == "__isabstractmethod__" {
if class_has_base_name(inst.class, "property") {
let mut is_abstract = false
match get_named_value(inst.dict, "fget") {
Some(value) =>
match abstract_flag_from_value(value) {
Ok(true) => is_abstract = true
Ok(false) => ()
Err(err) => return Err(err)
}
None => ()
}
if !is_abstract {
match get_named_value(inst.dict, "fset") {
Some(value) =>
match abstract_flag_from_value(value) {
Ok(true) => is_abstract = true
Ok(false) => ()
Err(err) => return Err(err)
}
None => ()
}
}
if !is_abstract {
match get_named_value(inst.dict, "fdel") {
Some(value) =>
match abstract_flag_from_value(value) {
Ok(true) => is_abstract = true
Ok(false) => ()
Err(err) => return Err(err)
}
None => ()
}
}
if !is_abstract {
match lookup_class_attr(inst.class, "__isabstractmethod__") {
Ok(Some(value)) =>
match truthy_from_value_with_env(value, globals, builtins, io) {
Ok(true) => is_abstract = true
Ok(false) => ()
Err(err) => return Err(err)
}
Ok(None) => ()
Err(err) => return Err(err)
}
}
return Ok(Value::Bool(is_abstract))
}
if class_has_base_name(inst.class, "classmethod") ||
class_has_base_name(inst.class, "staticmethod") {
match get_named_value(inst.dict, "func") {
Some(func_value) =>
match abstract_flag_from_value(func_value) {
Ok(v) => return Ok(Value::Bool(v))
Err(err) => return Err(err)
}
None => return Ok(Value::Bool(false))
}
}
}
if class_has_base_name(inst.class, "classmethod") ||
class_has_base_name(inst.class, "staticmethod") {
if attr == "__func__" {
match get_named_value(inst.dict, "func") {
Some(value) => return Ok(value)
None =>
return Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'" + inst.class.name + "' object has no attribute '__func__'",
),
)
}
}
}
if inst.class.name == "code" && attr == "replace" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "code.replace",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if inst.class.name == "frame" && attr == "clear" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "frame.clear",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if inst.class.name == "coroutine" && attr == "close" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "coroutine.close",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if inst.class.name == "super" {
// super(type, obj) proxy
let thisclass = match get_named_value(inst.dict, "__thisclass__") {
Some(Value::Class(k)) => k
_ =>
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"invalid super object".to_string(),
),
)
}
let self_value = match get_named_value(inst.dict, "__self__") {
Some(v) => v
None =>
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"invalid super object".to_string(),
),
)
}
let self_class = match get_named_value(inst.dict, "__self_class__") {
Some(Value::Class(k)) => k
_ =>
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"invalid super object".to_string(),
),
)
}
let mro = match class_mro(self_class) {
Ok(v) => v
Err(err) => return Err(err)
}
let mut after = false
for cls in mro {
if after {
let direct = get_named_value(cls.dict, attr)
if direct is Some(value) {
match value {
Value::Function(func) =>
// Bind like normal instance access.
// For class objects, only bind __init_subclass__ so it can be
// called as `super().__init_subclass__(...)` without passing
// `cls` explicitly (matches CPython behavior).
match self_value {
Value::Instance(_) =>
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: func,
self: self_value,
}),
)
Value::Class(_) =>
if attr == "__init_subclass__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: func,
self: self_value,
}),
)
}
_ => ()
}
_ => ()
}
// Apply descriptor semantics; owner is dynamic class
let obj_for_desc = match self_value {
Value::Instance(_) => self_value
_ => Value::None
}
match
apply_descriptor(value, obj_for_desc, Value::Class(self_class)) {
Ok(Some(v)) => return Ok(v)
Ok(None) => return Ok(value)
Err(err) => return Err(err)
}
}
}
if class_identity_equal(cls, thisclass) {
after = true
}
}
return Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'super' object has no attribute '" + attr + "'",
),
)
}
if inst.class.name == "generator" {
if attr == "send" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "send",
params: ["self", "value"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
}
if attr == "close" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "close",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
}
if attr == "throw" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "throw",
params: ["self", "exc", "val", "tb"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
}
}
if inst.class.name == "async_generator" {
let has_gen_id = match get_named_value(inst.dict, "gen_id") {
Some(_) => true
None => false
}
if attr == "__aiter__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__aiter__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
}
if attr == "__anext__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__anext__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
}
if has_gen_id {
if attr == "asend" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "asend",
params: ["self", "value"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
}
if attr == "athrow" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "athrow",
params: ["self", "exc", "val", "tb"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
}
if attr == "aclose" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "aclose",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
}
}
}
// Provide list methods on list subclasses (instances storing `$__list__`).
match get_named_value(inst.dict, list_storage_name) {
Some(Value::List(_)) =>
// Don't override user-defined methods (e.g. `class L(list): def insert...`).
// Only provide fallbacks for operations missing from the class dict.
if (match lookup_class_attr(inst.class, attr) {
Ok(Some(_)) => true
Ok(None) => false
Err(err) => return Err(err)
}) {
()
} else if attr == "__getitem__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__getitem__",
params: ["self", "index"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
} else if attr == "__setitem__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__setitem__",
params: ["self", "index", "value"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
} else if attr == "__delitem__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__delitem__",
params: ["self", "index"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
} else if attr == "append" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "append",
params: ["self", "object"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
} else if attr == "extend" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "extend",
params: ["self", "iterable"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
} else if attr == "insert" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "insert",
params: ["self", "index", "object"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
} else if attr == "remove" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "remove",
params: ["self", "value"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
} else if attr == "clear" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "clear",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
} else if attr == "pop" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "pop",
params: ["self", "index"],
defaults: [Value::Int(@bigint.BigInt::from_int(-1))],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
} else if attr == "copy" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "copy",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
} else if attr == "reverse" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "reverse",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
} else if attr == "sort" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "sort",
params: ["self", "key", "reverse"],
defaults: [Value::None, Value::Bool(false)],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
}
_ => ()
}
let class_attr = match lookup_class_attr(inst.class, attr) {
Ok(value) => value
Err(err) => return Err(err)
}
match class_attr {
Some(Value::Function(func)) =>
if func.body.length() == 0 &&
!func.name.has_prefix("__") &&
!func.name.contains(".") &&
// BaseException.add_note is an intrinsic method but should still be
// bound like a normal instance method.
func.name != "add_note" {
// Builtin function objects don't participate in method binding in
// CPython (e.g. `open` assigned as a class attribute).
Ok(Value::Function(func))
} else {
Ok(
Value::BoundMethod(BoundMethodValue::{
function: func,
self: Value::Instance(inst),
}),
)
}
Some(value) =>
match
apply_descriptor(
value,
Value::Instance(inst),
Value::Class(inst.class),
) {
Ok(Some(v)) => Ok(v)
Ok(None) => Ok(value)
Err(err) => Err(err)
}
None =>
if attr == "__reduce_ex__" {
// `pickle` relies on this existing on all heap objects. Implement it
// as a builtin intrinsic that returns a proper reduce tuple.
Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__reduce_ex__",
params: ["self", "proto"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Instance(inst),
}),
)
} else if attr == "__doc__" {
// Many built-in objects expose `__doc__` via their type. If a type
// doesn't provide one in moonpython yet, return a stub string so
// stdlib helpers can proceed.
Ok(Value::Str("".to_string()))
} else {
let msg = if inst.class.name == "module" {
let module_name = match get_named_value(inst.dict, "__name__") {
Some(Value::Str(text)) => text
_ => "".to_string()
}
"module '" + module_name + "' has no attribute '" + attr + "'"
} else {
"'" + inst.class.name + "' object has no attribute '" + attr + "'"
}
Err(make_runtime_error(RuntimeErrorKind::Attribute, msg))
}
}
}
Value::BoundMethod(bound_method) => {
if attr == "__func__" {
return Ok(Value::Function(bound_method.function))
}
if attr == "__self__" {
return Ok(bound_method.self)
}
get_attr_raw_from_value(
Value::Function(bound_method.function),
attr,
globals,
builtins,
io,
)
}
Value::Bool(v) => {
// bool is a subclass of int in CPython; expose the handful of numeric
// attributes/methods relied upon by the stdlib `numbers` ABCs.
let as_int = Value::Int(if v { 1N } else { 0N })
match attr {
"real" | "numerator" => return Ok(as_int)
"imag" => return Ok(Value::Int(0N))
"denominator" => return Ok(Value::Int(1N))
"conjugate" =>
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "int.conjugate",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
_ => ()
}
// Delegate to int attribute handling for methods like `bit_length`.
get_attr_raw_from_value(as_int, attr, globals, builtins, io)
}
Value::Int(_) => {
if attr == "__doc__" {
return Ok(Value::Str("".to_string()))
}
match attr {
"real" | "numerator" => return Ok(target)
"imag" => return Ok(Value::Int(0N))
"denominator" => return Ok(Value::Int(1N))
"conjugate" =>
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "int.conjugate",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
_ => ()
}
if attr == "bit_length" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "int.bit_length",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__index__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "int.__index__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "to_bytes" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "int.to_bytes",
params: ["self", "length", "byteorder", "signed"],
defaults: [Value::Str("big"), Value::Bool(false)],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__hash__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__hash__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
return Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'" +
type_name_from_value(target) +
"' object has no attribute '" +
attr +
"'",
),
)
}
Value::Float(value) =>
match attr {
"__doc__" => Ok(Value::Str("".to_string()))
"real" => Ok(Value::Float(value))
// CPython returns `0.0`; equality with `0` still holds in stdlib tests.
"imag" => Ok(Value::Float(0.0))
"as_integer_ratio" =>
Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "float.as_integer_ratio",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
"conjugate" =>
Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "float.conjugate",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'" +
type_name_from_value(target) +
"' object has no attribute '" +
attr +
"'",
),
)
}
Value::Complex(real, imag) =>
match attr {
"real" => Ok(Value::Float(real))
"imag" => Ok(Value::Float(imag))
"conjugate" =>
Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "complex.conjugate",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'" +
type_name_from_value(target) +
"' object has no attribute '" +
attr +
"'",
),
)
}
Value::Tuple(_) => {
if attr == "index" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "index",
params: ["self", "value", "start", "end"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "columns" {
match target {
Value::Tuple(values) =>
if values.length() >= 1 {
return Ok(values[0])
}
_ => ()
}
}
if attr == "lines" {
match target {
Value::Tuple(values) =>
if values.length() >= 2 {
return Ok(values[1])
}
_ => ()
}
}
return Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'" +
type_name_from_value(target) +
"' object has no attribute '" +
attr +
"'",
),
)
}
Value::Str(_) => {
if attr == "__doc__" {
// CPython exposes `__doc__` on many built-in instances (it resolves to the
// type's docstring). A stub string is sufficient for stdlib modules that
// only probe for the attribute's existence.
return Ok(Value::Str("".to_string()))
}
if attr == "startswith" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "startswith",
params: ["self", "prefix", "start", "end"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "find" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "find",
params: ["self", "sub", "start", "end"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "rfind" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "rfind",
params: ["self", "sub", "start", "end"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "count" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "count",
params: ["self", "sub", "start", "end"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "index" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "index",
params: ["self", "sub", "start", "end"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "rindex" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "rindex",
params: ["self", "sub", "start", "end"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "capitalize" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "capitalize",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "center" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "center",
params: ["self", "width", "fillchar"],
defaults: [Value::Str(" ")],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "isascii" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "isascii",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "isdigit" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "isdigit",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "isalnum" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "isalnum",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "isalpha" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "isalpha",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "isspace" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "isspace",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "islower" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "islower",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "isupper" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "isupper",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "istitle" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "istitle",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "isdecimal" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "isdecimal",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "isnumeric" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "isnumeric",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "partition" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "partition",
params: ["self", "sep"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "rpartition" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "rpartition",
params: ["self", "sep"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "expandtabs" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "expandtabs",
params: ["self", "tabsize"],
defaults: [Value::Int(8N)],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "translate" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "translate",
params: ["self", "table"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "endswith" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "endswith",
params: ["self", "suffix", "start", "end"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "isidentifier" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "isidentifier",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "upper" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "upper",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "lower" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "lower",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "casefold" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "casefold",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "replace" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "replace",
params: ["self", "old", "new", "count"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "split" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "split",
params: ["self", "sep", "maxsplit"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "rsplit" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "rsplit",
params: ["self", "sep", "maxsplit"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "splitlines" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "splitlines",
params: ["self", "keepends"],
defaults: [Value::Bool(false)],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "strip" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "strip",
params: ["self", "chars"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "lstrip" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "lstrip",
params: ["self", "chars"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "rstrip" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "rstrip",
params: ["self", "chars"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "removeprefix" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "removeprefix",
params: ["self", "prefix"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "removesuffix" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "removesuffix",
params: ["self", "suffix"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "encode" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "encode",
params: ["self", "encoding", "errors"],
defaults: [Value::Str("utf-8"), Value::Str("strict")],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "format" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "format",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "join" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "str.join",
params: ["self", "iterable"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'str' object has no attribute '" + attr + "'",
),
)
}
Value::MemoryView(_) => {
if attr == "format" {
// CPython exposes memoryview.format as a read-only attribute.
// We currently treat all memoryviews as a stream of bytes.
return Ok(Value::Str("B"))
}
if attr == "ndim" {
// CPython uses ndim to distinguish between multi-dimensional views.
// We currently only support a 1-D view of bytes.
return Ok(Value::Int(1))
}
if attr == "tobytes" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "memoryview.tobytes",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "cast" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "memoryview.cast",
params: ["self", "format", "shape"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'memoryview' object has no attribute '" + attr + "'",
),
)
}
Value::Bytes(_) => {
if attr == "decode" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "decode",
params: ["self", "encoding", "errors"],
defaults: [Value::Str("utf-8"), Value::Str("strict")],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "strip" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "strip",
params: ["self", "bytes"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "find" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "find",
params: ["self", "sub", "start", "end"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "rfind" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "rfind",
params: ["self", "sub", "start", "end"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "startswith" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "startswith",
params: ["self", "prefix", "start", "end"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "endswith" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "endswith",
params: ["self", "suffix", "start", "end"],
defaults: [Value::None, Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "upper" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bytes.upper",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "rstrip" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bytes.rstrip",
params: ["self", "chars"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "join" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bytes.join",
params: ["self", "iterable"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "translate" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bytes.translate",
params: ["self", "table"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "hex" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "hex",
params: ["self", "sep", "bytes_per_sep"],
defaults: [Value::None, Value::Int(1N)],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'" +
type_name_from_value(target) +
"' object has no attribute '" +
attr +
"'",
),
)
}
Value::List(_) => {
if attr == "append" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "append",
params: ["self", "object"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "extend" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "extend",
params: ["self", "iterable"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "insert" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "insert",
params: ["self", "index", "object"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "remove" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "remove",
params: ["self", "value"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "clear" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "clear",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "pop" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "pop",
params: ["self", "index"],
defaults: [Value::Int(@bigint.BigInt::from_int(-1))],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "copy" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "copy",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "reverse" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "reverse",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "sort" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "sort",
params: ["self", "key", "reverse"],
defaults: [Value::None, Value::Bool(false)],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__setitem__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__setitem__",
params: ["self", "index", "value"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'list' object has no attribute '" + attr + "'",
),
)
}
Value::Dict(_) => {
if attr == "fromkeys" {
let dict_class = builtin_class_from_name("dict", builtins)
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "dict.fromkeys",
params: ["cls", "iterable", "value"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Class(dict_class),
}),
)
}
if attr == "get" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "get",
params: ["self", "key", "default"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "pop" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "pop",
params: ["self", "key", "default"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "popitem" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "popitem",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "setdefault" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "setdefault",
params: ["self", "key", "default"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "keys" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "keys",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "values" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "values",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "items" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "items",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "copy" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "copy",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "update" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "update",
params: ["self", "other"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "clear" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "clear",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "copy" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "copy",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__getitem__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__getitem__",
params: ["self", "key"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__setitem__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bytearray.__setitem__",
params: ["self", "key", "value"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__delitem__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__delitem__",
params: ["self", "key"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__iter__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__iter__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__len__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__len__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__contains__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__contains__",
params: ["self", "key"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'dict' object has no attribute '" + attr + "'",
),
)
}
Value::Set(_) => {
if attr == "add" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "add",
params: ["self", "value"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "remove" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "remove",
params: ["self", "value"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "discard" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "discard",
params: ["self", "value"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "pop" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "pop",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "clear" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "clear",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "update" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "update",
params: ["self", "iterable"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "issuperset" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "issuperset",
params: ["self", "other"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "issubset" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "issubset",
params: ["self", "other"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__contains__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__contains__",
params: ["self", "item"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__iter__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__iter__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__len__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__len__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "difference" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "difference",
params: ["self", "other"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "difference_update" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "difference_update",
params: ["self", "other"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "intersection" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "intersection",
params: ["self", "other"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "intersection_update" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "intersection_update",
params: ["self", "other"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'set' object has no attribute '" + attr + "'",
),
)
}
Value::ByteArray(_) => {
if attr == "decode" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "decode",
params: ["self", "encoding", "errors"],
defaults: [Value::Str("utf-8"), Value::Str("strict")],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "upper" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bytearray.upper",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "rstrip" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bytearray.rstrip",
params: ["self", "chars"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "translate" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bytearray.translate",
params: ["self", "table"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "append" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "append",
params: ["self", "value"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "extend" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "extend",
params: ["self", "iterable"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "copy" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "copy",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__setitem__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bytearray.__setitem__",
params: ["self", "key", "value"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
if attr == "__delitem__" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bytearray.__delitem__",
params: ["self", "key"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: target,
}),
)
}
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'bytearray' object has no attribute '" + attr + "'",
),
)
}
Value::Function(func) =>
if attr == "__dict__" {
if func.body.length() == 0 && is_builtin_class_name(func.name) {
let klass = builtin_class_from_name(func.name, builtins)
let pairs : Array[(Value, Value)] = []
for item in klass.dict {
pairs.push((Value::Str(item.0), item.1))
}
Ok(Value::Dict(pairs))
} else {
Ok(Value::Dict(function_attr_dict(func)))
}
} else {
let pairs = function_attr_dict(func)
let index = match dict_find_index(pairs, Value::Str(attr)) {
Ok(v) => v
Err(err) => return Err(err)
}
if index is Some(i) {
return Ok(pairs[i].1)
}
if attr == "__defaults__" {
if func.defaults.length() == 0 {
Ok(Value::None)
} else {
let values : Array[Value] = []
for v in func.defaults {
values.push(v)
}
Ok(Value::Tuple(values))
}
} else if attr == "__kwdefaults__" {
// Keyword-only defaults are not modeled yet.
Ok(Value::None)
} else if attr == "__annotations__" {
// Default to an empty annotations dict for introspection helpers.
Ok(Value::Dict([]))
} else if attr == "__code__" {
let mut co_filename = "".to_string()
if func.body.length() == 0 {
co_filename = "".to_string()
} else {
let mut env = globals
for pair in func.closure {
if pair.0 == globals_capture_name {
match pair.1 {
Value::Instance(inst) => env = inst.dict
_ => ()
}
break
}
}
match get_named_value(env, "__file__") {
Some(Value::Str(text)) => co_filename = text
_ => ()
}
}
let mut flags = 0N
if func.is_generator {
if func.is_async {
flags = flags + 512N // CO_ASYNC_GENERATOR
} else {
flags = flags + 32N // CO_GENERATOR
}
} else if func.is_async {
flags = flags + 128N // CO_COROUTINE
}
// Provide enough metadata for inspect.Signature to model positional-only
// args (/), kw-only args (*), and varargs/kwargs.
let mut has_posonly_marker = false
for param in func.params {
if param_is_posonly_marker(param) {
has_posonly_marker = true
break
}
}
let mut saw_posonly_marker = false
let mut saw_kw_only_marker = false
let mut posonlyargcount = 0
let mut pos_or_kw_count = 0
let mut kwonlyargcount = 0
let mut varargs = false
let mut varkw = false
let varnames : Array[String] = []
for param in func.params {
if param_is_posonly_marker(param) {
saw_posonly_marker = true
continue
}
if param_is_kw_only_marker(param) {
saw_kw_only_marker = true
continue
}
if param_is_vararg(param) {
varargs = true
}
if param_is_kwarg(param) {
varkw = true
}
match normalize_param_name(param) {
Some(name) => varnames.push(name)
None => ()
}
// Only count "real" arguments (exclude *args/**kwargs from argcount).
if param_is_vararg(param) || param_is_kwarg(param) {
continue
}
if saw_kw_only_marker {
kwonlyargcount += 1
} else if has_posonly_marker && !saw_posonly_marker {
posonlyargcount += 1
} else {
pos_or_kw_count += 1
}
}
if varargs {
flags = flags + 4N // CO_VARARGS
}
if varkw {
flags = flags + 8N // CO_VARKEYWORDS
}
let firstlineno = match
get_named_value(func.closure, firstlineno_capture_name) {
Some(Value::Int(v)) => v
_ => 1N
}
let argcount = posonlyargcount + pos_or_kw_count
let code_value = make_code_instance_with_meta(
flags,
co_filename,
func.name,
firstlineno,
argcount,
posonlyargcount,
kwonlyargcount,
varnames,
)
let future = get_global_value(
resolve_function_globals(func, globals),
"__mpython_future_annotations__",
)
is Some(Value::Bool(true))
if code_value is Value::Instance(instance) {
match
compile_stmts_to_bc_with_future(
local_annotation_statements(func.body),
func.name,
co_filename,
future,
) {
Ok(code) => {
let positions = code.spans.map(fn(span) {
match span {
Some(span) =>
Value::Tuple([
Value::Int(@bigint.BigInt::from_int(span.line)),
Value::Int(@bigint.BigInt::from_int(span.line)),
Value::Int(@bigint.BigInt::from_int(span.column - 1)),
Value::Int(
@bigint.BigInt::from_int(
span.column - 1 + span.end - span.start,
),
),
])
None =>
Value::Tuple([
Value::None,
Value::None,
Value::None,
Value::None,
])
}
})
set_named_value(
instance.dict,
"__mpython_co_positions",
Value::List(positions),
)
}
Err(_) => ()
}
}
Ok(code_value)
} else if attr == "__closure__" {
let cells : Array[Value] = []
for pair in func.closure {
if is_cell_value(pair.1) {
cells.push(pair.1)
}
}
if cells.length() == 0 {
Ok(Value::None)
} else {
Ok(Value::Tuple(cells))
}
} else if attr == "__globals__" {
let mut env = globals
for pair in func.closure {
if pair.0 == globals_capture_name {
match pair.1 {
Value::Instance(inst) => env = inst.dict
_ => ()
}
break
}
}
let pairs : Array[(Value, Value)] = []
for item in env {
pairs.push((Value::Str(item.0), item.1))
}
Ok(Value::Dict(pairs))
} else if attr == "__module__" {
let mut env = globals
for pair in func.closure {
if pair.0 == globals_capture_name {
match pair.1 {
Value::Instance(inst) => env = inst.dict
_ => ()
}
break
}
}
let module_name = match get_named_value(env, "__name__") {
Some(Value::Str(name)) => name
_ =>
if func.body.length() == 0 {
"builtins".to_string()
} else {
"__main__".to_string()
}
}
Ok(Value::Str(module_name))
} else if attr == "__doc__" {
fn unwrap_stmt(node : Stmt) -> Stmt {
match node {
Stmt::WithSpan(span~, stmt~) => {
let _ = span
unwrap_stmt(stmt)
}
other => other
}
}
fn unwrap_expr(node : Expr) -> Expr {
match node {
Expr::WithSpan(span~, expr~) => {
let _ = span
unwrap_expr(expr)
}
other => other
}
}
if func.body.length() > 0 {
match unwrap_stmt(func.body[0]) {
Stmt::ExprStmt(expr) =>
match unwrap_expr(expr) {
Expr::Literal(Literal::Str(text)) => Ok(Value::Str(text))
_ => Ok(Value::None)
}
_ => Ok(Value::None)
}
} else {
Ok(Value::None)
}
} else if attr == "__qualname__" {
Ok(Value::Str(func.name))
} else if attr == "__name__" {
Ok(Value::Str(func.name))
} else if func.body.length() == 0 && is_builtin_class_name(func.name) {
get_attr_raw_from_value(
Value::Class(builtin_class_from_name(func.name, builtins)),
attr,
globals,
builtins,
io,
)
} else {
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"'function' object has no attribute '" + attr + "'",
),
)
}
}
Value::Class(klass) =>
if attr == "__name__" {
match get_named_value(klass.dict, "__name__") {
Some(Value::Str(v)) => Ok(Value::Str(v))
_ => Ok(Value::Str(klass.name))
}
} else if attr == "__qualname__" {
match get_named_value(klass.dict, "__qualname__") {
Some(Value::Str(v)) => Ok(Value::Str(v))
_ => Ok(Value::Str(klass.name))
}
} else if attr == "__module__" {
match get_named_value(klass.dict, "__module__") {
Some(v) => Ok(v)
None => Ok(Value::Str("builtins"))
}
} else if attr == "__subclasses__" {
Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "__subclasses__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Class(klass),
}),
)
} else if attr == "__flags__" {
// CPython exposes internal type flags on most type objects.
// The stdlib uses TPFLAGS_IS_ABSTRACT (1<<20) via inspect.isabstract().
// Provide that bit based on __abstractmethods__.
let mut is_abstract = false
match lookup_class_attr(klass, "__abstractmethods__") {
Ok(Some(Value::Set(items))) =>
if items.length() > 0 {
is_abstract = true
}
Ok(Some(Value::Tuple(items))) =>
if items.length() > 0 {
is_abstract = true
}
Ok(Some(Value::List(items))) =>
if items.length() > 0 {
is_abstract = true
}
Ok(Some(_)) => ()
Ok(None) => ()
Err(err) => return Err(err)
}
Ok(Value::Int(if is_abstract { 1048576N } else { 0N }))
} else if attr == "__doc__" {
Ok(Value::None)
} else if attr == "__bases__" {
let bases : Array[Value] = []
for base in klass.bases {
bases.push(base)
}
Ok(Value::Tuple(bases))
} else if attr == "__new__" && klass.name == "type" {
Ok(
Value::Function(FunctionValue::{
name: "type.__new__",
params: ["cls", "name", "bases", "dict"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
} else if attr == "__dict__" {
let pairs : Array[(Value, Value)] = []
for item in klass.dict {
pairs.push((Value::Str(item.0), item.1))
}
Ok(Value::Dict(pairs))
} else if klass.name == "code" && attr == "__init__" {
// Introspection-only stub so stdlib utilities (e.g. unittest.mock)
// can access `types.CodeType.__init__`.
Ok(
Value::Function(FunctionValue::{
name: "code.__init__",
params: ["self", "*args", "**kwargs"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
} else if attr == "__mro__" {
let mro = match class_mro(klass) {
Ok(value) => value
Err(err) => return Err(err)
}
let values : Array[Value] = []
for item in mro {
values.push(Value::Class(item))
}
Ok(Value::Tuple(values))
} else if klass.name == "list" && attr == "append" {
// Expose `list.append` as a best-effort descriptor-like stub so pure
// Python stdlib modules (e.g. multiprocessing.reduction) can introspect
// it via `type(list.append)`.
Ok(
Value::Function(FunctionValue::{
name: "list.append",
params: ["self", "object"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
} else if klass.name == "list" && attr == "copy" {
Ok(
Value::Function(FunctionValue::{
name: "list.copy",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
} else if klass.name == "str" && attr == "casefold" {
Ok(
Value::Function(FunctionValue::{
name: "str.casefold",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
} else if klass.name == "int" && attr == "__add__" {
// Similar to `list.append`, provide an introspection stub for
// `int.__add__` used by the stdlib pickling machinery.
Ok(
Value::Function(FunctionValue::{
name: "int.__add__",
params: ["self", "other"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
} else if klass.name == "dict" && attr == "copy" {
Ok(
Value::Function(FunctionValue::{
name: "dict.copy",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
} else if klass.name == "dict" && attr == "fromkeys" {
Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "dict.fromkeys",
params: ["cls", "iterable", "value"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Class(klass),
}),
)
} else if klass.name == "bytes" && attr == "fromhex" {
Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bytes.fromhex",
params: ["cls", "string"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Class(klass),
}),
)
} else if klass.name == "bytearray" && attr == "fromhex" {
Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bytearray.fromhex",
params: ["cls", "string"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Class(klass),
}),
)
} else if klass.name == "set" && attr == "copy" {
Ok(
Value::Function(FunctionValue::{
name: "set.copy",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
} else if klass.name == "bytearray" && attr == "copy" {
Ok(
Value::Function(FunctionValue::{
name: "bytearray.copy",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
} else if klass.name == "object" &&
(
attr == "__repr__" ||
attr == "__str__" ||
attr == "__format__" ||
attr == "__reduce_ex__"
) {
Ok(
Value::Function(FunctionValue::{
name: "object." + attr,
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
} else if klass.name == "int" && attr == "from_bytes" {
Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "int.from_bytes",
params: ["cls", "bytes", "byteorder", "signed"],
defaults: [Value::Str("big"), Value::Bool(false)],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Class(klass),
}),
)
} else if klass.name == "bool" && attr == "from_bytes" {
Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "bool.from_bytes",
params: ["cls", "bytes", "byteorder", "signed"],
defaults: [Value::Str("big"), Value::Bool(false)],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Class(klass),
}),
)
} else if klass.name == "int" && attr == "bit_length" {
Ok(
Value::Function(FunctionValue::{
name: "int.bit_length",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
} else if attr == "__code__" && klass.name == "function" {
Ok(make_code_instance(0N))
} else if attr == "__closure__" && klass.name == "function" {
Ok(Value::None)
} else if attr == "__globals__" && klass.name == "function" {
Ok(
Value::Instance(InstanceValue::{
class: builtin_class_from_name("member_descriptor", builtins),
dict: [],
}),
)
} else {
match lookup_class_attr(klass, attr) {
Ok(Some(Value::Function(func))) => {
if attr == "__repr__" &&
get_named_value(klass.dict, "__repr__") is None {
// Built-in container types provide a synthetic `X.__repr__` via
// fallback lookup. For subclasses like `class S(set): ...`, make
// sure `S.__repr__` resolves to `set.__repr__` so stdlib dispatch
// keyed by `type(x).__repr__` (e.g. pprint) behaves reasonably.
match class_mro(klass) {
Ok(mro) =>
for cls in mro {
if cls.name == "dict" ||
cls.name == "list" ||
cls.name == "tuple" ||
cls.name == "set" ||
cls.name == "frozenset" {
return Ok(
Value::Function(FunctionValue::{
name: cls.name + ".__repr__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
}
}
Err(_) => ()
}
}
if attr == "__init_subclass__" || attr == "__class_getitem__" {
// PEP 487 / PEP 560: these are implicitly class methods on the class object.
Ok(
Value::BoundMethod(BoundMethodValue::{
function: func,
self: Value::Class(klass),
}),
)
} else {
Ok(Value::Function(func))
}
}
Ok(Some(value)) =>
match apply_descriptor(value, Value::None, Value::Class(klass)) {
Ok(Some(v)) => Ok(v)
Ok(None) => Ok(value)
Err(err) => Err(err)
}
Ok(None) => {
// Metaclass attributes (including descriptors like `@property`) are
// looked up if the attribute is not found on the class itself.
let mut meta = builtin_class_from_name("type", builtins)
match get_named_value(klass.dict, "__mpython_metaclass__") {
Some(Value::Class(m)) => meta = m
_ => ()
}
match lookup_class_attr(meta, attr) {
Ok(Some(Value::Function(func))) =>
// Method binding for metaclass functions (e.g. EnumMeta.__members__,
// ABCMeta.register). Mirror the instance binding behavior.
if func.body.length() == 0 &&
!func.name.has_prefix("__") &&
!func.name.contains(".") {
return Ok(Value::Function(func))
} else {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: func,
self: Value::Class(klass),
}),
)
}
Ok(Some(value)) =>
match
apply_descriptor(
value,
Value::Class(klass),
Value::Class(meta),
) {
Ok(Some(v)) => return Ok(v)
Ok(None) => return Ok(value)
Err(err) => return Err(err)
}
Ok(None) => ()
Err(err) => return Err(err)
}
if attr == "__repr__" && is_builtin_class_name(klass.name) {
// Provide distinct intrinsic `__repr__` descriptors for built-in
// types so stdlib dispatch tables keyed by `type(x).__repr__`
// (e.g. pprint) don't collapse to a single function object.
return Ok(
Value::Function(FunctionValue::{
name: klass.name + ".__repr__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
}
let mut is_enum = false
let mut member_type_value = Value::Class(
builtin_class_from_name("object", builtins),
)
match class_mro(klass) {
Ok(mro) =>
for cls in mro {
if cls.name == "Enum" ||
cls.name == "Flag" ||
cls.name == "IntFlag" ||
cls.name == "StrEnum" {
is_enum = true
}
if cls.name == "int" {
member_type_value = Value::Class(cls)
} else if cls.name == "str" {
member_type_value = Value::Class(cls)
}
}
Err(err) => return Err(err)
}
if is_enum {
match attr {
"_use_args_" => {
let value = Value::Bool(false)
set_named_value(klass.dict, attr, value)
return Ok(value)
}
"_member_names_" => {
let value = Value::List([])
set_named_value(klass.dict, attr, value)
return Ok(value)
}
"_member_map_" => {
let value = Value::Dict([])
set_named_value(klass.dict, attr, value)
return Ok(value)
}
"_value2member_map_" => {
let value = Value::Dict([])
set_named_value(klass.dict, attr, value)
return Ok(value)
}
"_unhashable_values_" => {
let value = Value::List([])
set_named_value(klass.dict, attr, value)
return Ok(value)
}
"_member_type_" => {
set_named_value(klass.dict, attr, member_type_value)
return Ok(member_type_value)
}
"_value_repr_" => {
let mut repr_value : Value? = None
for pair in builtins {
if pair.0 == "repr" {
repr_value = Some(pair.1)
break
}
}
let value = match repr_value {
Some(v) => v
None =>
Value::Function(FunctionValue::{
name: "repr",
params: ["obj"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
})
}
set_named_value(klass.dict, attr, value)
return Ok(value)
}
_ => ()
}
}
if attr == "__repr__" ||
attr == "__str__" ||
attr == "__format__" ||
attr == "__reduce_ex__" {
return Ok(
Value::Function(FunctionValue::{
name: attr,
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
)
}
if attr == "mro" {
return Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "mro",
params: ["cls"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Class(klass),
}),
)
}
if attr == "register" {
Ok(
Value::BoundMethod(BoundMethodValue::{
function: FunctionValue::{
name: "register",
params: ["cls", "subclass"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
},
self: Value::Class(klass),
}),
)
} else {
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"type object '" +
klass.name +
"' has no attribute '" +
attr +
"'",
),
)
}
}
Err(err) => Err(err)
}
}
}
}
///|
fn get_attr_from_value(
target : Value,
attr : String,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Value, RuntimeError] {
fn enrich_attribute_error(err0 : RuntimeError) -> RuntimeError {
match err0.kind {
RuntimeErrorKind::Attribute => ()
_ => return err0
}
match err0.exc_value {
Some(Value::Instance(inst)) => {
let need_name = match get_named_value(inst.dict, "name") {
Some(Value::Str(_)) => false
_ => true
}
let need_obj = match get_named_value(inst.dict, "obj") {
Some(Value::None) | None => true
Some(_) => false
}
if need_name {
set_named_value(inst.dict, "name", Value::Str(attr))
}
if need_obj {
set_named_value(inst.dict, "obj", target)
}
// If we injected metadata, ensure the raised value is the enriched instance.
if need_name || need_obj {
return RuntimeError::{
kind: err0.kind,
message: err0.message,
span: err0.span,
traceback: err0.traceback,
traceback_envs: err0.traceback_envs,
exc_type: err0.exc_type,
exc_args: err0.exc_args,
exc_value: Some(Value::Instance(inst)),
exc_cause: err0.exc_cause,
exc_context: err0.exc_context,
exc_suppress_context: err0.exc_suppress_context,
}
}
return err0
}
Some(_) => return err0
None => ()
}
// Populate AttributeError.obj / AttributeError.name so traceback.py can
// compute "Did you mean" suggestions via dir(obj).
match get_named_value(builtins, "AttributeError") {
Some(Value::Class(klass)) =>
match
call_callable_with_env(
Value::Class(klass),
[Value::Str(err0.message)],
[],
globals,
builtins,
io,
) {
Ok(Value::Instance(inst)) => {
set_named_value(inst.dict, "name", Value::Str(attr))
set_named_value(inst.dict, "obj", target)
RuntimeError::{
kind: err0.kind,
message: err0.message,
span: err0.span,
traceback: err0.traceback,
traceback_envs: err0.traceback_envs,
exc_type: err0.exc_type,
exc_args: err0.exc_args,
exc_value: Some(Value::Instance(inst)),
exc_cause: err0.exc_cause,
exc_context: err0.exc_context,
exc_suppress_context: err0.exc_suppress_context,
}
}
_ => err0
}
_ => err0
}
}
// 1) If instance defines __getattribute__, use it.
// 2) Otherwise do the default lookup.
// 3) If default lookup fails and instance defines __getattr__, call it.
match target {
Value::Instance(inst) => {
// __getattribute__ (instance hook)
match lookup_class_attr(inst.class, "__getattribute__") {
Ok(Some(Value::Function(f))) => {
// Call through the normal call path so intrinsic implementations
// (e.g. object.__getattribute__) are handled.
let bound = Value::BoundMethod(BoundMethodValue::{
function: f,
self: target,
})
let result = call_callable_with_env(
bound,
[Value::Str(attr)],
[],
globals,
builtins,
io,
)
match result {
Ok(v) => return Ok(v)
Err(err0) =>
match err0.kind {
RuntimeErrorKind::Attribute =>
// __getattr__ fallback only for AttributeError
match lookup_class_attr(inst.class, "__getattr__") {
Ok(Some(Value::Function(f2))) => {
let name_val2 = Value::Str(attr)
let out = (eval_function_with_kwargs_ref.val)(
f2,
[target, name_val2],
[],
globals,
builtins,
io,
)
return match out {
Ok(v) => Ok(v)
Err(e) => Err(enrich_attribute_error(e))
}
}
Ok(Some(_)) =>
return Err(
make_runtime_error(
RuntimeErrorKind::Type,
"__getattr__ must be a function".to_string(),
),
)
Ok(None) => return Err(enrich_attribute_error(err0))
Err(err) => return Err(err)
}
_ => return Err(err0)
}
}
}
Ok(Some(_)) =>
return Err(
make_runtime_error(
RuntimeErrorKind::Type,
"__getattribute__ must be a function".to_string(),
),
)
Ok(None) => ()
Err(err) => return Err(err)
}
// Default lookup
let raw = get_attr_raw_from_value(target, attr, globals, builtins, io)
match raw {
Ok(v) => Ok(v)
Err(err0) =>
// __getattr__ fallback
match lookup_class_attr(inst.class, "__getattr__") {
Ok(Some(Value::Function(f))) => {
let name_val = Value::Str(attr)
let out = (eval_function_with_kwargs_ref.val)(
f,
[target, name_val],
[],
globals,
builtins,
io,
)
match out {
Ok(v) => Ok(v)
Err(e) => Err(enrich_attribute_error(e))
}
}
Ok(Some(_)) =>
Err(
make_runtime_error(
RuntimeErrorKind::Type,
"__getattr__ must be a function".to_string(),
),
)
Ok(None) => Err(enrich_attribute_error(err0))
Err(err) => Err(err)
}
}
}
_ => get_attr_raw_from_value(target, attr, globals, builtins, io)
}
}
///|
fn set_attr_raw_on_value(
target : Value,
attr : String,
value : Value,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Unit, RuntimeError] {
fn apply_descriptor_set(
class_attr : Value,
obj : Value,
value : Value,
) -> Result[Bool, RuntimeError] {
// Built-in descriptor: property(fget, fset, fdel, doc)
match class_attr {
Value::Instance(inst) =>
if class_has_base_name(inst.class, "property") {
match get_named_value(inst.dict, "fset") {
Some(fset) => {
let args : Array[Value] = [obj, value]
let _ = match
call_callable_with_env(fset, args, [], globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
return Ok(true)
}
None =>
return Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"can't set attribute".to_string(),
),
)
}
}
_ => ()
}
let desc_class = match class_attr {
Value::Instance(d) => Some(d.class)
Value::Class(d) => Some(d)
_ => None
}
if desc_class is None {
return Ok(false)
}
let desc_class = desc_class.unwrap()
let set_opt = match lookup_class_attr(desc_class, "__set__") {
Ok(v) => v
Err(err) => return Err(err)
}
match set_opt {
Some(Value::Function(set_fn)) => {
let args : Array[Value] = [class_attr, obj, value]
match
(eval_function_with_kwargs_ref.val)(
set_fn,
args,
[],
globals,
builtins,
io,
) {
Ok(_v) => Ok(true)
Err(err) => Err(err)
}
}
_ => Ok(false)
}
}
match target {
Value::Instance(inst) => {
let class_attr = match lookup_class_attr(inst.class, attr) {
Ok(v) => v
Err(err) => return Err(err)
}
match class_attr {
Some(descr) => {
let handled = match apply_descriptor_set(descr, target, value) {
Ok(v) => v
Err(err) => return Err(err)
}
if handled {
return Ok(())
}
}
None => ()
}
set_named_value(inst.dict, attr, value)
Ok(())
}
Value::Function(func) => {
let pairs = function_attr_dict(func)
let _ = match dict_set_item(pairs, Value::Str(attr), value) {
Ok(v) => v
Err(err) => return Err(err)
}
Ok(())
}
Value::Class(klass) => {
// NOTE: we don't implement metaclass descriptors yet.
set_named_value(klass.dict, attr, value)
Ok(())
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"cannot set attribute".to_string(),
),
)
}
}
///|
fn set_attr_on_value(
target : Value,
attr : String,
value : Value,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Unit, RuntimeError] {
// __setattr__ (instance hook)
match target {
Value::Instance(inst) =>
match lookup_class_attr(inst.class, "__setattr__") {
Ok(Some(Value::Function(f))) => {
let bound = Value::BoundMethod(BoundMethodValue::{
function: f,
self: target,
})
let _ = match
call_callable_with_env(
bound,
[Value::Str(attr), value],
[],
globals,
builtins,
io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
return Ok(())
}
Ok(Some(_)) =>
return Err(
make_runtime_error(
RuntimeErrorKind::Type,
"__setattr__ must be a function".to_string(),
),
)
Ok(None) => ()
Err(err) => return Err(err)
}
_ => ()
}
set_attr_raw_on_value(target, attr, value, globals, builtins, io)
}
///|
fn delete_attr_raw_on_value(
target : Value,
attr : String,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Unit, RuntimeError] {
fn apply_descriptor_delete(
class_attr : Value,
obj : Value,
) -> Result[Bool, RuntimeError] {
// Built-in descriptor: property(fget, fset, fdel, doc)
match class_attr {
Value::Instance(inst) =>
if class_has_base_name(inst.class, "property") {
match get_named_value(inst.dict, "fdel") {
Some(fdel) => {
let _ = match
call_callable_with_env(fdel, [obj], [], globals, builtins, io) {
Ok(v) => v
Err(err) => return Err(err)
}
return Ok(true)
}
None =>
return Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"can't delete attribute".to_string(),
),
)
}
}
_ => ()
}
let desc_class = match class_attr {
Value::Instance(d) => Some(d.class)
Value::Class(d) => Some(d)
_ => None
}
if desc_class is None {
return Ok(false)
}
let desc_class = desc_class.unwrap()
let del_opt = match lookup_class_attr(desc_class, "__delete__") {
Ok(v) => v
Err(err) => return Err(err)
}
match del_opt {
Some(Value::Function(del_fn)) => {
let args : Array[Value] = [class_attr, obj]
match
(eval_function_with_kwargs_ref.val)(
del_fn,
args,
[],
globals,
builtins,
io,
) {
Ok(_v) => Ok(true)
Err(err) => Err(err)
}
}
_ => Ok(false)
}
}
match target {
Value::Instance(inst) => {
let class_attr = match lookup_class_attr(inst.class, attr) {
Ok(v) => v
Err(err) => return Err(err)
}
match class_attr {
Some(descr) => {
let handled = match apply_descriptor_delete(descr, target) {
Ok(v) => v
Err(err) => return Err(err)
}
if handled {
return Ok(())
}
}
None => ()
}
for i = 0; i < inst.dict.length(); i = i + 1 {
if inst.dict[i].0 == attr {
let _ = inst.dict.remove(i)
return Ok(())
}
}
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"attribute not found".to_string(),
),
)
}
Value::Class(klass) => {
for i = 0; i < klass.dict.length(); i = i + 1 {
if klass.dict[i].0 == attr {
let _ = klass.dict.remove(i)
return Ok(())
}
}
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"attribute not found".to_string(),
),
)
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::Attribute,
"attribute not supported".to_string(),
),
)
}
}
///|
fn delete_attr_on_value(
target : Value,
attr : String,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Result[Unit, RuntimeError] {
// __delattr__ (instance hook)
match target {
Value::Instance(inst) =>
match lookup_class_attr(inst.class, "__delattr__") {
Ok(Some(Value::Function(f))) => {
let bound = Value::BoundMethod(BoundMethodValue::{
function: f,
self: target,
})
let _ = match
call_callable_with_env(
bound,
[Value::Str(attr)],
[],
globals,
builtins,
io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
return Ok(())
}
Ok(Some(_)) =>
return Err(
make_runtime_error(
RuntimeErrorKind::Type,
"__delattr__ must be a function".to_string(),
),
)
Ok(None) => ()
Err(err) => return Err(err)
}
_ => ()
}
delete_attr_raw_on_value(target, attr, globals, builtins, io)
}