Repository navigation
Conversation
[Medium impact] The change reaches PHP programs that call the new builtin in compiled code or `eval()`. Fix runtime callback names for eval-created objects before merging.
|
Add get_debug_type() as a registry builtin with a shared contract, a builtin! home, the __rt_debug_type_name runtime helper on AArch64 and x86_64, and a Magician eval implementation. Anonymous classes are named the way php-src does (class@anonymous, Parent@anonymous, Interface@anonymous from the first declared interface). The checker records that spelling in ClassInfo::anonymous_debug_name and the runtime data emits it in a new _class_debug_name_entries table parallel to _class_name_entries. Eval reads the table through __elephc_eval_value_object_debug_class_name, and compiled code with an eval context asks the bridge through a new get_debug_type class-lookup kind.
The C-ABI wrappers behind get_class() and get_debug_type() inside eval rejected the reserved incomplete-class id (-2) with their unsigned bounds check, so an unserialized object of an unknown class made the eval call fail. Answer __PHP_Incomplete_Class on both targets, as the native helpers already do.
… get_debug_type An iterable operand is a type-erased pointer to an array or a Traversable object, but get_debug_type() always answered "array" for it. Dispatch on the heap kind at run time so a Generator or ArrayIterator reports its class name.
The answer depends on a boxed cell's tag, an object's class id and a resource payload that an explicit close flips, so the call is not state-independent. Declare READS_HEAP like get_class().
228426f to
854919b
Compare
Without a php code fence the generated page joined the example lines into one Markdown paragraph. Regenerate the builtin docs and registry.
get_debug_type was missing from the generic runtime-callable list, so its callable policy was static-only: a callback name known only at run time (array_map($name, ...), $name(...), call_user_func($name, ...)) failed with "does not name a supported callable". Like gettype it takes one mixed value and answers a static type name, so the generic wrapper applies.
…g_type callable policy
| @@ -2099,6 +2105,7 @@ impl RuntimeFnId { | |||
| | RuntimeFnId::ArrayProduct | |||
| | RuntimeFnId::CloneWith | |||
| | RuntimeFnId::Count | |||
| | RuntimeFnId::GetDebugType | |||
There was a problem hiding this comment.
Callbacks lose eval class names
A runtime-selected get_debug_type callback reports stdClass instead of class@anonymous for an object returned by eval('return new class {};'). The newly enabled callback uses a generated wrapper without an eval context. It therefore reads the native backing class instead of the eval-declared class, giving logs and error messages the wrong type.
Make the wrapper consult the object's eval context and add a regression test with an eval-created object.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ir/runtime_fn.rs
Line: 2108
Comment:
**Callbacks lose eval class names**
A runtime-selected `get_debug_type` callback reports `stdClass` instead of `class@anonymous` for an object returned by `eval('return new class {};')`. The newly enabled callback uses a generated wrapper without an eval context. It therefore reads the native backing class instead of the eval-declared class, giving logs and error messages the wrong type.
Make the wrapper consult the object's eval context and add a regression test with an eval-created object.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Summary
Third family of the missing-builtins PR from the #1886 split (review item 19). Before this PR,
get_debug_type()was undefined. It is now a real builtin:get_defined_functions()['internal']lists it, andReflectionFunction::isInternal()returns true;builtin!home (src/builtins/types/get_debug_type.rs);__rt_debug_type_name, on AArch64 and x86_64;It follows PHP 8.5 for every kind of value:
null,bool,int,float,string,array.Closure, enums report the enum name, and generators reportGenerator. An incomplete unserialized object reports__PHP_Incomplete_Class.resource (stream),resource (stream filter)orresource (stream-context), andresource (closed)once it has been closed, whether the value is boxed or not.class, followed by@anonymous. That givesclass@anonymous,App\Base@anonymousandApp\Shape@anonymous. The implicitStringablethat a__toString()method adds does not count.Anonymous-class names in the runtime metadata
elephc names anonymous classes
class@anonymous#N(namespaced, e.g.App\class@anonymous#0). The flattened interface list cannot tell a declaredStringableapart from the implicit one. To keep the change small:ClassInfogets one field,anonymous_debug_name: Option<String>. The checker computes it from the declaredextends/implementsand leaves itNonefor named classes._class_debug_name_entries. It has the same row shape and bound (_class_name_count) as_class_name_entries. A named class reuses its existing_class_name_<id>bytes. Only an anonymous class adds its own literal. The cost is 16 bytes per emitted class id.get_class(),var_dump()and Reflection are unchanged.Eval
get_debug_type()names eval-declared anonymous classes from theirEvalClassparent and interfaces.__elephc_eval_value_object_debug_class_name. This is a twin of the existing class-name wrapper; both are now emitted by one parametrized function per target.__elephc_eval_object_class_namebridge with a new lookup kind (get_debug_type). Objects of eval-declared classes, which own no generated class id, are therefore named correctly.Repro (no issue yet)
No issue lists
get_debug_type()(#859 coverspreg_quote,version_compare,uniqidandstristr).Known differences
new class extends arrayobject {}givesarrayobject@anonymous). elephc uses the declared spelling of the class (ArrayObject@anonymous), because name resolution canonicalizes names before the checker sees them.get_resource_type()agrees with them:stream_context_create()is typed as a stream resource, so it reportsresource (stream);stream_context_create()is boxed as an int.Tests
On main 0842fca every new test fails with
Undefined function(or, for Reflection,Function get_debug_type() does not exist). On this branch they pass:codegen::casts_and_constants::get_debug_type::test_get_debug_type_names_scalars_arrays_and_objectscodegen::casts_and_constants::get_debug_type::test_get_debug_type_names_anonymous_classes_like_phpcodegen::casts_and_constants::get_debug_type::test_get_debug_type_names_open_and_closed_resourcescodegen::casts_and_constants::get_debug_type::test_get_debug_type_is_an_internal_function. It covers eval in both directions, string callables,call_user_func,array_map, first-class callables, case-insensitive and namespaced calls,get_defined_functions()['internal']andisInternal().error_tests::misc::string_and_type_builtins::test_error_get_debug_type_wrong_argsEvery expected output was measured on PHP 8.5.10. Other changes:
examples/core-introspectionshows the builtin.This PR and the open #1924 / #1925 builtin PRs are independent; each is based on main. They bump the same contract counts (
registry.rs/support.rs) and regenerate the same docs, so whichever lands later needs a rebase and a fresh docs regeneration. I will do it.Refs #1886
AI assistance
This PR was prepared with substantial help from Claude Code (Anthropic). The behavior for each value kind was checked against PHP 8.5 before and after implementation.
🤖 Generated with Claude Code