-
-
Notifications
You must be signed in to change notification settings - Fork 159
fix(runtime): complete class semantics tail #8630
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a2ff7e3
434b2e5
a91f840
c69e2fe
8066961
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| Completed the remaining class-semantics tail: derived construction, per-evaluation | ||
| private elements, computed and static fields, native built-in subclasses, and | ||
| class reflection now follow JavaScript behavior consistently. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -485,6 +485,9 @@ pub(super) fn compile_method( | |
| pending_labels: Vec::new(), | ||
| classes, | ||
| this_stack: vec![this_slot], | ||
| super_called_stack: Vec::new(), | ||
| shared_super_scope_active: false, | ||
| lexical_this_uses_derived_binding: false, | ||
| inline_ctor_return: Vec::new(), | ||
| new_target_stack: Vec::new(), | ||
| class_stack: vec![class.name.clone()], | ||
|
|
@@ -728,6 +731,14 @@ pub(super) fn compile_method( | |
| // as uninitialized register values (read as NaN-boxed undefined). | ||
| let is_constructor_method = method.name == format!("{}_constructor", class.name); | ||
| if is_constructor_method { | ||
| if class.extends.is_some() | ||
| || class.extends_name.is_some() | ||
| || class.native_extends.is_some() | ||
| || class.extends_expr.is_some() | ||
| { | ||
| crate::expr::this_super_call::push_shared_super_called_slot(&mut ctx); | ||
| ctx.shared_super_scope_active = true; | ||
| } | ||
| // Stage field initializers around the parent body chain so leaf | ||
| // fields can read state set by parent body (Refs #420): | ||
| // - has extends: apply only ancestors here; self-fields apply | ||
|
|
@@ -953,7 +964,7 @@ pub(super) fn compile_method( | |
| } | ||
| // Load `this` from the this_stack. | ||
| let this_slot = ctx.this_stack.last().cloned(); | ||
| let this_box = if let Some(slot) = this_slot { | ||
| let this_box = if let Some(ref slot) = this_slot { | ||
| ctx.block().load(DOUBLE, &slot) | ||
| } else { | ||
| undef_lit.clone() | ||
|
|
@@ -973,7 +984,20 @@ pub(super) fn compile_method( | |
| // real signature (see codegen/mod.rs). | ||
| ctx.pending_declares | ||
| .push((ctor_sym.clone(), DOUBLE, ctor_param_types)); | ||
| let _ = ctx.block().call(DOUBLE, &ctor_sym, &ctor_args); | ||
| let parent_result = ctx.block().call(DOUBLE, &ctor_sym, &ctor_args); | ||
| if let Some(this_slot) = this_slot { | ||
| let current_this = ctx.block().load(DOUBLE, &this_slot); | ||
| let bound_this = ctx.block().call( | ||
| DOUBLE, | ||
| "js_ctor_return_override", | ||
| &[ | ||
| (DOUBLE, ¤t_this), | ||
| (DOUBLE, &parent_result), | ||
| (crate::types::I32, "0"), | ||
| ], | ||
| ); | ||
| ctx.block().store(DOUBLE, &bound_this, &this_slot); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -1009,7 +1033,16 @@ pub(super) fn compile_method( | |
| // .pathname` threw. Forward this synthesized ctor's params to the | ||
| // runtime dynamic-parent super dispatcher, mirroring the explicit | ||
| // `Expr::SuperCall` dynamic-parent path in `expr/this_super_call.rs`. | ||
| if builtin_parent_runtime.is_none() && class.extends_expr.is_some() { | ||
| let parent_is_uncallable_builtin = class | ||
| .extends_name | ||
| .as_deref() | ||
| .map(crate::expr::is_other_builtin_constructor_name) | ||
| .unwrap_or(false) | ||
| && class.extends_name.as_deref() != Some("SharedArrayBuffer"); | ||
| if builtin_parent_runtime.is_none() | ||
| && class.extends_expr.is_some() | ||
| && !parent_is_uncallable_builtin | ||
| { | ||
|
Comment on lines
+1036
to
+1045
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Find lowering sites that populate both extends_name and extends_expr.
set -euo pipefail
rg -n -C 10 'extends_expr:\s*Some' --type=rust crates/perry-hir/src
echo '--- is_other_builtin_constructor_name definition ---'
rg -n -C 20 'fn is_other_builtin_constructor_name' --type=rustRepository: PerryTS/perry Length of output: 151 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- candidate Class definitions and fields ---'
rg -n -C 20 '\b(struct|class)\s+Class\b|extends_name|extends_expr' --glob '*.rs' crates/perry-codegen crates/perry-hir crates
echo '--- all assignments and struct initializers involving heritage fields ---'
rg -n -C 8 'extends_name\s*:|extends_expr\s*:|extends_name\s*=|extends_expr\s*=' --glob '*.rs' .
echo '--- builtin predicate ---'
rg -n -C 20 'is_other_builtin_constructor_name' --glob '*.rs' .Repository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- Class definition and documentation ---'
rg -n -C 18 'pub struct Class|struct Class|extends_name:|extends_expr:' crates/perry-hir/src --glob '*.rs' | head -n 240
echo '--- dynamic extends-name inference ---'
sed -n '120,205p' crates/perry-hir/src/lower/misc.rs
echo '--- factory specialization heritage rewrite ---'
sed -n '810,875p' crates/perry-transform/src/inline/factory_specialize.rs
echo '--- codegen gate and surrounding logic ---'
sed -n '990,1085p' crates/perry-codegen/src/codegen/method.rs
echo '--- builtin constructor predicate ---'
rg -n -C 20 'is_other_builtin_constructor_name' crates --glob '*.rs'Repository: PerryTS/perry Length of output: 50369 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- Class heritage documentation ---'
sed -n '175,245p' crates/perry-hir/src/ir/decl.rs
echo '--- non-test writes to extends_name ---'
rg -n -C 6 'extends_name\s*=\s*Some|extends_name\s*:' crates/perry-hir/src crates/perry-transform/src crates/perry-codegen/src --glob '*.rs' \
| rg -v 'tests|extends_name: None|extends_name: Some' | head -n 300
echo '--- non-test writes to extends_expr ---'
rg -n -C 8 'extends_expr\s*=\s*Some|extends_expr\s*:' crates/perry-hir/src crates/perry-transform/src crates/perry-codegen/src --glob '*.rs' \
| rg -v 'tests|extends_expr: None|extends_expr: Some' | head -n 300
echo '--- invocation order for dynamic heritage inference ---'
rg -n -C 12 'infer_dynamic_extends_names|lower_class_from_ast|extends_expr' crates/perry-hir/src/lower crates/perry-hir/src/lower_decl --glob '*.rs' | head -n 500Repository: PerryTS/perry Length of output: 50369 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- factory-related heritage tests and fixtures ---'
rg -n -C 8 'makeBare|infer_dynamic_extends_names|extends (Map|Set|WeakMap|WeakSet|Array|ArrayBuffer|DataView|Boolean|Number|String|Date|RegExp|Promise|Function|BigInt|Symbol)' . \
--glob '*.rs' --glob '*.ts' --glob '*.tsx' --glob '*.js' --glob '*.mjs' --glob '*.cjs' \
| head -n 500
echo '--- all explicit ClassRef builtin names in lowering/transform code ---'
rg -n -C 5 'ClassRef\("(Map|Set|WeakMap|WeakSet|Array|ArrayBuffer|SharedArrayBuffer|DataView|Boolean|Number|String|Date|RegExp|Promise|Function|BigInt|Symbol)"' crates --glob '*.rs' | head -n 300
echo '--- focused static verifier for the two field invariant ---'
python3 - <<'PY'
from pathlib import Path
misc = Path("crates/perry-hir/src/lower/misc.rs").read_text()
specialize = Path("crates/perry-transform/src/inline/factory_specialize.rs").read_text()
method = Path("crates/perry-codegen/src/codegen/method.rs").read_text()
checks = {
"infer has extends_expr guard": "let Some(expr) = class.extends_expr.as_deref() else" in misc,
"infer assigns extends_name": "class.extends_name = Some(parent_name.clone());" in misc,
"infer does not clear extends_expr": "class.extends_expr = None" not in misc,
"specialization reads extends_expr mutably": "if let Some(extends_expr) = cloned.extends_expr.as_mut()" in specialize,
"specialization assigns extends_name": "cloned.extends_name = Some(parent_name.clone());" in specialize,
"specialization does not clear extends_expr": "cloned.extends_expr = None" not in specialize,
"codegen gate requires extends_expr": "class.extends_expr.is_some()" in method,
"codegen gate excludes builtin": "!parent_is_uncallable_builtin" in method,
}
for name, ok in checks.items():
print(f"{name}: {'PASS' if ok else 'FAIL'}")
PYRepository: PerryTS/perry Length of output: 50369 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- method codegen context and builtin_parent_runtime assignment ---'
rg -n -C 14 'builtin_parent_runtime|is_constructor_method|force_ctor_call|extends_expr' crates/perry-codegen/src/codegen/method.rs | head -n 700
echo '--- class heritage lowering implementation ---'
rg -n -C 12 'extends_expr|heritage_lexically_shadowed|extends_name' crates/perry-hir/src/lower_decl crates/perry-hir/src/lower --glob '*.rs' \
| rg -v 'tests|extends_name: None|extends_expr: None' | head -n 700
echo '--- factory specialization entry points and parent substitution ---'
rg -n -C 16 'specialize|factory|param_subst|substitute_locals|dynamic_parent_expr' crates/perry-transform/src/inline/factory_specialize.rs | head -n 900Repository: PerryTS/perry Length of output: 50369 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- complete Ident heritage routing ---'
sed -n '371,528p' crates/perry-hir/src/lower_decl/class_decl.rs
echo '--- complete Member heritage routing ---'
sed -n '528,690p' crates/perry-hir/src/lower_decl/class_decl.rs
echo '--- class-expression heritage routing ---'
rg -n -C 18 'lower_class_from_ast|Handle extends|extract_member_class_name|parent_name' crates/perry-hir/src/lower_decl/class_decl.rs | tail -n 420
echo '--- dynamic-parent registration and super dispatch ---'
rg -n -C 16 'RegisterClassParentDynamic|js_fetch_or_value_super|js_get_dynamic_parent_value' crates/perry-hir/src crates/perry-codegen/src --glob '*.rs' | head -n 600Repository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- lowering of Ident and Member heritage expressions ---'
rg -n -C 24 'fn lower_class_heritage_expr|lower_class_heritage_expr\(' crates/perry-hir/src/lower_decl crates/perry-hir/src/lower --glob '*.rs' | head -n 500
echo '--- dynamic super dispatcher builtin handling ---'
sed -n '480,575p' crates/perry-codegen/src/expr/this_super_call.rs
echo '--- focused verifier for name-only builtin classification ---'
python3 - <<'PY'
from pathlib import Path
decl = Path("crates/perry-hir/src/lower_decl/class_decl.rs").read_text()
method = Path("crates/perry-codegen/src/codegen/method.rs").read_text()
ident_case = '(None, Some(parent_name), None, Some(Box::new(expr)))'
member_case = 'let resolved = ctx.lookup_class(&parent_name)'
print("unknown Ident stores extends_name and extends_expr:",
ident_case in decl)
print("named Member stores extends_name and extends_expr:",
member_case in decl and decl.count(ident_case) >= 2)
print("builtin gate uses extends_name:",
"map(crate::expr::is_other_builtin_constructor_name)" in method)
print("builtin gate does not inspect extends_expr:",
"parent_is_uncallable_builtin" in method and
"class.extends_expr" not in method[method.index("let parent_is_uncallable_builtin"):
method.index("if builtin_parent_runtime.is_none()")])
PYRepository: PerryTS/perry Length of output: 45239 Base the builtin exclusion on the resolved parent expression. Class lowering stores both fields for unknown identifiers and named member heritage. A dynamic parent named 🤖 Prompt for AI Agents |
||
| if let Some(cid) = ctx.class_ids.get(&class.name).copied().filter(|c| *c != 0) { | ||
| let undef_lit = | ||
| crate::nanbox::double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)); | ||
|
|
@@ -1049,7 +1082,7 @@ pub(super) fn compile_method( | |
| Some(slot) => ctx.block().load(DOUBLE, &slot), | ||
| None => undef_lit.clone(), | ||
| }; | ||
| let _ = ctx.block().call( | ||
| let parent_result = ctx.block().call( | ||
| DOUBLE, | ||
| "js_fetch_or_value_super", | ||
| &[ | ||
|
|
@@ -1059,9 +1092,28 @@ pub(super) fn compile_method( | |
| (I64, &args_len), | ||
| ], | ||
| ); | ||
| if let Some(this_slot) = ctx.this_stack.last().cloned() { | ||
| let current_this = ctx.block().load(DOUBLE, &this_slot); | ||
| let bound_this = ctx.block().call( | ||
| DOUBLE, | ||
| "js_ctor_return_override", | ||
| &[ | ||
| (DOUBLE, ¤t_this), | ||
| (DOUBLE, &parent_result), | ||
| (crate::types::I32, "0"), | ||
| ], | ||
| ); | ||
| ctx.block().store(DOUBLE, &bound_this, &this_slot); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // The synthesized default derived constructor has now completed | ||
| // its implicit `super(...arguments)` path. Publish that fact to | ||
| // both this standalone function and any arrow closures before | ||
| // evaluating the class's own instance fields. | ||
| crate::expr::this_super_call::bind_derived_this_after_super(&mut ctx); | ||
|
|
||
| // Apply self field initializers AFTER the parent body chain has | ||
| // run, so they can read state set by the parent body (e.g. drizzle's | ||
| // PgText.enumValues = this.config.enumValues — this.config is set | ||
|
|
@@ -1102,6 +1154,31 @@ pub(super) fn compile_method( | |
| && !crate::lower_call::ctor_body_uses_this(&ctor.body)) | ||
| && !crate::lower_call::ctor_body_has_value_return(&ctor.body) | ||
| }); | ||
| // Standalone constructor symbols use the same internal completion slot as | ||
| // an inlined `new`: every explicit/bare return funnels to one block, where | ||
| // constructor return-override semantics are applied against the CURRENT | ||
| // `this` binding. This matters for a derived `super()` whose base returns | ||
| // a replacement object — an implicit/bare return must publish that object | ||
| // to the caller, not `undefined` (which would make the caller retain its | ||
| // original pre-super allocation). | ||
| let standalone_ctor_return = if is_constructor_method && !ctor_no_super_throw { | ||
| let result_slot = ctx.func.alloca_entry(DOUBLE); | ||
| let undef = crate::nanbox::double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)); | ||
| ctx.block().store(DOUBLE, &undef, &result_slot); | ||
| let after_idx = ctx.new_block("standalone.ctor.return.after"); | ||
| let target = crate::expr::InlineCtorReturn { | ||
| result_slot, | ||
| after_label: ctx.block_label(after_idx), | ||
| is_derived: class.extends.is_some() | ||
| || class.extends_name.is_some() | ||
| || class.native_extends.is_some() | ||
| || class.extends_expr.is_some(), | ||
| }; | ||
| ctx.inline_ctor_return.push(target.clone()); | ||
| Some((target, after_idx)) | ||
| } else { | ||
| None | ||
| }; | ||
| if ctor_no_super_throw { | ||
| ctx.block() | ||
| .call(DOUBLE, "js_throw_reference_error_this_before_super", &[]); | ||
|
|
@@ -1119,16 +1196,47 @@ pub(super) fn compile_method( | |
| })?; | ||
| } | ||
|
|
||
| if let Some((target, after_idx)) = standalone_ctor_return.as_ref() { | ||
| let _ = ctx | ||
| .inline_ctor_return | ||
| .pop() | ||
| .expect("standalone constructor return target"); | ||
| if !ctx.block().is_terminated() { | ||
| ctx.block().br(&target.after_label); | ||
| } | ||
| ctx.current_block = *after_idx; | ||
| } | ||
|
|
||
| if !ctx.block().is_terminated() { | ||
| let undef = crate::nanbox::double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)); | ||
| let return_value = if let Some((target, _)) = standalone_ctor_return.as_ref() { | ||
| let raw = ctx.block().load(DOUBLE, &target.result_slot); | ||
| let this_value = ctx | ||
| .this_stack | ||
| .last() | ||
| .cloned() | ||
| .map(|slot| ctx.block().load(DOUBLE, &slot)) | ||
| .unwrap_or_else(|| undef.clone()); | ||
| crate::lower_call::emit_ctor_return_override( | ||
| &mut ctx, | ||
| &this_value, | ||
| &raw, | ||
| target.is_derived, | ||
| ) | ||
| } else { | ||
| undef.clone() | ||
| }; | ||
| if ctx.shared_super_scope_active { | ||
| ctx.block().call_void("js_derived_super_scope_pop", &[]); | ||
| } | ||
| if method.is_async { | ||
| let handle = ctx | ||
| .block() | ||
| .call(I64, "js_promise_resolved", &[(DOUBLE, &undef)]); | ||
| let boxed = crate::expr::nanbox_pointer_inline_pub(ctx.block(), &handle); | ||
| ctx.block().ret(DOUBLE, &boxed); | ||
| } else { | ||
| ctx.block().ret(DOUBLE, &undef); | ||
| ctx.block().ret(DOUBLE, &return_value); | ||
| } | ||
| } | ||
| let ic_globals = std::mem::take(&mut ctx.ic_globals); | ||
|
|
@@ -1600,6 +1708,9 @@ pub(super) fn compile_static_method( | |
| pending_labels: Vec::new(), | ||
| classes, | ||
| this_stack: vec![this_slot], | ||
| super_called_stack: Vec::new(), | ||
| shared_super_scope_active: false, | ||
| lexical_this_uses_derived_binding: false, | ||
| inline_ctor_return: Vec::new(), | ||
| new_target_stack: Vec::new(), | ||
| // A static method's `this` is the class constructor (bound above to | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 2747
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 11784
Increment the workspace patch version
Update
[workspace.package].versioninCargo.tomland the matching**Current Version:**line inCLAUDE.md.🤖 Prompt for AI Agents
Source: Coding guidelines