///|
/// Reflect `[[Get]]` / `[[Set]]` value-path internal operations relocated from
/// `interpreter/stdlib/builtins_reflect.mbt` so that stdlib calls a runtime
/// operation instead of reaching into PropertyBag representation internals
/// (`bag.properties` / `bag.descriptors` / `bag.symbol_properties` /
/// `bag.symbol_descriptors`) for the receiver-aware get/set paths (architecture
/// redesign Stage 8, following #335 own-property-keys, #338 integrity, #339
/// keys/values/entries, #340 prototype ops, #341 Proxy revoke).
///
/// These reproduce the existing Reflect.get / Reflect.set behavior verbatim.
/// They are the VALUE path and are intentionally distinct from #340's
/// `prototype_ops.mbt` (the prototype path); do not unify them.
///
/// Preserved-as-is (NOT "fixed" here):
/// - `reflect_get_with_receiver` reimplements receiver-aware `[[Get]]` rather
/// than routing through `Interpreter::get_property`, because `get_property`
/// uses its `obj` argument as both lookup target and receiver and has no
/// receiver parameter — routing through it would silently drop Reflect.get's
/// custom (3rd-argument) receiver. Aligning this with the internal
/// receiver-preserving walk (`get_property_from_prototype`) is a separate
/// behavior-preserving refactor, not this move.
/// - `reflect_set_preflight_blocks` (removed in #342): the target-side
/// own-descriptor check (OrdinarySet §10.1.9.2 steps 1 / 3.a / 4) was
/// redundant — `set_property` already raises TypeError in strict mode for
/// getter-only and non-writable-data own descriptors, and Reflect.set
/// delegates with `strict=true` and catches TypeError → Bool(false).
/// The pre-flight was a no-op for non-Object targets (Array/Map/Set/Promise)
/// and equivalent to the existing TypeError path for Object targets.
///|
/// target receiver-aware `[[Get]]` backing for Reflect.get (§28.1.7). Argument
/// validation and the non-object TypeError check stay at the stdlib boundary;
/// this op performs the receiver-aware descriptor walk verbatim. The former
/// `try_get_with_receiver` closure's captures are threaded as parameters; its
/// body is unchanged.
pub fn reflect_get_with_receiver(
interp : Interpreter,
target : Value,
key : Value,
receiver : Value,
loc : @token.Loc,
) -> Value raise Error {
let key_str = match key {
Symbol(_) => ""
_ => key.to_string()
}
// Find an own accessor getter and call it with `receiver`; otherwise fall
// back to the own data slot. An own descriptor whose getter is None and a
// missing descriptor both fall through to the stored property value.
fn try_get_with_receiver(obj : Value) -> Value? raise {
guard obj is Object(data) else { return None }
match key {
Value::Symbol(sym) =>
match data.bag.symbol_descriptors.get(sym.id) {
Some({ getter: Some(getter), .. }) =>
Some(interp.call_value(getter, receiver, [], loc))
_ => data.bag.symbol_properties.get(sym.id)
}
_ =>
match data.bag.descriptors.get(key_str) {
Some({ getter: Some(getter), .. }) =>
Some(interp.call_value(getter, receiver, [], loc))
_ => data.bag.properties.get(key_str)
}
}
}
// Check target first
match try_get_with_receiver(target) {
Some(v) => v
None => {
// Walk prototype chain
let mut current = match target {
Object(data) => data.prototype
_ => Null
}
while current is Object(data) {
match try_get_with_receiver(current) {
Some(v) => return v
None => current = data.prototype
}
}
// Fall back to regular property access unconditionally.
// Accessor getters with receiver were already handled in
// try_get_with_receiver above; remaining cases are data reads
// or absent properties, neither of which depends on receiver equality.
match key {
Symbol(_) => interp.get_computed_property(target, key, loc)
_ => interp.get_property(target, key_str, loc)
}
}
}
}