Skip to content

fix(frontend): materialize object upcasts for fixed call arguments - #83

Merged
SuperIceCN merged 4 commits into
masterfrom
fix/frontend/callable-reveiver
Sep 27, 2026
Merged

SuperIceCN merged 4 commits into
masterfrom
fix/frontend/callable-reveiver

Conversation

@SuperIceCN

Copy link
Copy Markdown
Collaborator

Summary

Frontend lowering now materializes strict object subclass arguments passed to ancestor-typed fixed call parameters into target-typed temporaries via AssignInsn, covering builtin constructors, engine methods, and GDCC calls. This un-rejects previously failing calls such as Callable(subclass_instance, &"method") while preserving backend exact-match constructor semantics.

What changed

  • Frontend lowering (FrontendBodyLoweringSession): added materializeCallArgumentBoundaryValue with a strict-object-subclass peek (isStrictObjectSubclassArgument); only the fixed-parameter loop routes here, vararg tails and DYNAMIC_FALLBACK calls are untouched.
  • Boundary temp naming/ownership contract documented: upcast temps use cfg_boundary_<use>_upcast_<n> and follow standard slot-write ownership with function-scoped __finally__ cleanup.
  • Tests: new CallArgumentObjectUpcastCodegenTest (end-to-end codegen), plus lowering (FrontendLoweringBodyInsnPassTest) and C codegen (CConstructInsnGenTest) coverage for Callable/Signal constructor rejection anchors and upcast temp ownership.
  • Docs: new frontend_call_argument_object_upcast_implementation.md source-of-truth; updated ownership spec §3.6, implicit conversion matrix, (un)pack implementation §4.2/§4.3, frontend rules, and signal support anchors.

Why

  • The backend builtin constructor path matches argument type names exactly, so a subclass-typed slot was rejected where Godot accepts the implicit subclass-to-ancestor conversion (e.g. Callable(token, &"bump") with a custom Object subclass).
  • Engine/builtin method routes share the same argument-type invariant even though their backend could upcast on its own, so the fix is applied uniformly at the single shared boundary-materialization entry rather than per-consumer local branches.

Affected packages/files

  • src/main/java/gd/script/gdcc/frontend/lowering/pass/body/FrontendBodyLoweringSession.java
  • src/test/java/gd/script/gdcc/backend/c/build/CallArgumentObjectUpcastCodegenTest.java (new)
  • src/test/java/gd/script/gdcc/backend/c/gen/CConstructInsnGenTest.java
  • src/test/java/gd/script/gdcc/frontend/lowering/FrontendLoweringBodyInsnPassTest.java
  • doc/gdcc_ownership_lifecycle_spec.md, doc/module_impl/frontend/frontend_call_argument_object_upcast_implementation.md (new), frontend_implicit_conversion_matrix.md, frontend_lowering_(un)pack_implementation.md, frontend_rules.md, frontend_signal_support.md

Validation

  • script/run-gradle-targeted-tests.sh --tests CallArgumentObjectUpcastCodegenTest,CConstructInsnGenTest,FrontendLoweringBodyInsnPassTest
    (wraps ./gradlew test --tests ... --no-daemon --info --console=plain)

Result: BUILD SUCCESSFUL

Risks / Notes

  • Behavior contract impact: boundary materialization temps (pack/unpack/null-object/intrinsic cast/builtin constructor/fixed-call-argument upcast) are now explicitly documented as ordinary managed locals with function-scoped lifetime (ownership spec §3.6); a reference-managed argument may stay alive past the consuming call until function exit. This matches existing cfg_tmp_* evaluation-temp behavior, so no Godot-observable divergence.
  • Non-goals: call-scoped narrowing of boundary-temp lifetimes; no change to vararg tails or DYNAMIC_FALLBACK routes.

Key behaviors covered (Optional)

  • Strict object subclass -> ancestor fixed-parameter upcast materialized as target-typed temp + AssignInsn for builtin, engine, and GDCC call routes.
  • Same-name object pairs stay ALLOW_DIRECT; unrelated object types keep the shared entry's rejection behavior.
  • Constructor route does not retain the receiver (CConstructInsnGenTest asserts no own_object/try_own_object).

Diff stats (Optional)

  • 10 files changed, 769 insertions(+), 5 deletions(-)

Breaking changes (Optional)

  • None

Related docs (Optional)

  • doc/module_impl/frontend/frontend_call_argument_object_upcast_implementation.md
  • doc/gdcc_ownership_lifecycle_spec.md §3.6
  • doc/module_impl/frontend/frontend_lowering_(un)pack_implementation.md §4.2/§4.3

- Capture the builtin constructor rejection for an object subclass argument and the runtime receiver-lifetime behavior of method-reference sugar
- Document the planned call-boundary upcast fix while preserving Godot semantics and backend exact matching
- Clarify the subclass-to-ancestor upcast exception for fixed call arguments
- Document its boundary-temp naming and limit it to exact fixed-parameter routes
- Mark the documentation-first step complete in the implementation plan
- Lower subclass arguments into parameter-typed temporaries for builtin, engine, and GDCC calls
- Preserve exact-match backend constructor handling and verify upcast temporary ownership
- Add lowering and codegen coverage for Callable, Signal, and regression boundaries
- Complete implementation documentation and acceptance checklist
- Replace the completed implementation plan with a source-of-truth contract
- Update documentation links and remove obsolete lowering guidance
Copilot AI lite review requested due to automatic review settings September 27, 2026 02:54
@SuperIceCN
SuperIceCN merged commit b244ea6 into master Sep 27, 2026
5 checks passed
@SuperIceCN
SuperIceCN deleted the fix/frontend/callable-reveiver branch September 27, 2026 02:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Generated temporary allocation can collide with a user variable and overwrite its state.

Review effort: Lite
Findings: 1 High severity

Open (1)
What changed in this PR

Fixes frontend lowering by materializing strict object-subclass arguments into ancestor-typed temporaries for fixed call parameters.

Changes:

  • Adds AssignInsn-based upcast materialization.
  • Adds lowering, backend, and codegen tests.
  • Documents conversion, ownership, and temporary naming contracts.
File Summary
src/​test/​java/​gd/​script/​gdcc/​frontend/​lowering/​FrontendLoweringBodyInsnPassTest.java Tests lowering behavior and regression paths.
src/​test/​java/​gd/​script/​gdcc/​backend/​c/​gen/​CConstructInsnGenTest.java Tests constructor matching and ownership.
src/​test/​java/​gd/​script/​gdcc/​backend/​c/​build/​CallArgumentObjectUpcastCodegenTest.java Verifies generated C and ownership behavior.
src/​main/​java/​gd/​script/​gdcc/​frontend/​lowering/​pass/​body/​FrontendBodyLoweringSession.java Implements fixed-call object upcast materialization.
doc/​module_impl/​frontend/​frontend_signal_support.md Documents Callable and Signal support anchors.
doc/​module_impl/​frontend/​frontend_rules.md Documents temporary naming rules.
doc/​module_impl/​frontend/​frontend_lowering_(un)pack_implementation.md Documents boundary materialization behavior.
doc/​module_impl/​frontend/​frontend_implicit_conversion_matrix.md Updates implicit conversion rules.
doc/​module_impl/​frontend/​frontend_call_argument_object_upcast_implementation.md Documents the implementation contract.
doc/​gdcc_ownership_lifecycle_spec.md Documents boundary temporary lifetime and ownership.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +1476 to +1477
var upcastSlotId = nextBoundaryMaterializationSlotId(boundaryUse, "upcast");
ensureVariable(upcastSlotId, targetType);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants