`obj. ` resolution shared by `handle_load_attr` and `exec_call_method`.
(&self, obj: Val, name: &str)
| 105 | |
| 106 | // `obj.<name>` resolution shared by `handle_load_attr` and `exec_call_method`. |
| 107 | pub(crate) fn resolve_attr(&self, obj: Val, name: &str) -> Result<AttrLookup, VmErr> { |
| 108 | let bare = crate::modules::parser::ssa_strip(name); |
| 109 | |
| 110 | // Module attr: linear scan; the table is sized for around 30 entries. |
| 111 | if obj.is_heap() |
| 112 | && let HeapObj::Module(mod_name, attrs) = self.heap.get(obj) { |
| 113 | if let Some((_, v)) = attrs.iter().find(|(n, _)| n == bare) { |
| 114 | return Ok(AttrLookup::ModuleAttr(*v)); |
| 115 | } |
| 116 | return Err(VmErr::Attribute(s!("module '", str mod_name, "' has no attribute '", str bare, "'"))); |
| 117 | } |
| 118 | |
| 119 | // ExcInstance attr: only `e.args` is defined. |
| 120 | if obj.is_heap() |
| 121 | && let HeapObj::ExcInstance(_, args) = self.heap.get(obj) { |
| 122 | if bare == "args" { return Ok(AttrLookup::ExcArgs(args.clone())); } |
| 123 | let ty = self.type_name(obj); |
| 124 | return Err(VmErr::Attribute(s!("'", str ty, "' object has no attribute '", str bare, "'"))); |
| 125 | } |
| 126 | |
| 127 | // `__name__` on callables and types resolves to their declared name. |
| 128 | if obj.is_heap() && bare == "__name__" { |
| 129 | let resolved = match self.heap.get(obj) { |
| 130 | HeapObj::Func(fi, _, _) => self.function_names.get(*fi).cloned(), |
| 131 | HeapObj::Type(n) => Some(n.clone()), |
| 132 | HeapObj::Class(n, _, _) => Some(n.clone()), |
| 133 | _ => None, |
| 134 | }; |
| 135 | if let Some(n) = resolved { return Ok(AttrLookup::Name(n)); } |
| 136 | } |
| 137 | |
| 138 | // Class attr: `MyClass.method` returns the unbound function (no `self` prepended). |
| 139 | if obj.is_heap() |
| 140 | && let HeapObj::Class(cls_name, _, _) = self.heap.get(obj) { |
| 141 | if let Some((v, _)) = self.lookup_class_member(obj, bare) { |
| 142 | // `staticmethod` accessed on the class itself unwraps to the plain function. |
| 143 | if v.is_heap() && let HeapObj::StaticMethod(func) = self.heap.get(v) { |
| 144 | return Ok(AttrLookup::ClassMember(*func)); |
| 145 | } |
| 146 | return Ok(AttrLookup::ClassMember(v)); |
| 147 | } |
| 148 | let cls_name = cls_name.clone(); |
| 149 | return Err(VmErr::Attribute(s!("type object '", str &cls_name, "' has no attribute '", str bare, "'"))); |
| 150 | } |
| 151 | |
| 152 | // Instance attribute lookup: check `__dict__` first, then the class chain (direct + bases). |
| 153 | if obj.is_heap() |
| 154 | && let HeapObj::Instance(cls_val, attrs) = self.heap.get(obj) { |
| 155 | let cls_val = *cls_val; |
| 156 | let found = attrs.borrow().entries.iter() |
| 157 | .find(|(k, _)| k.is_heap() && matches!(self.heap.get(*k), HeapObj::Str(s) if s == name)) |
| 158 | .map(|(_, v)| *v); |
| 159 | if let Some(v) = found { return Ok(AttrLookup::InstanceField(v)); } |
| 160 | if let Some((mv, defining)) = self.lookup_class_member(cls_val, bare) { |
| 161 | // Guard on is_heap before heap.get: a non-heap data member (e.g. a wide int) would otherwise be read as a heap pointer and index a garbage slot. |
| 162 | if mv.is_heap() { |
| 163 | match self.heap.get(mv) { |
| 164 | // A Property member triggers getter invocation in `handle_load_attr`. |
no test coverage detected