Skip to content

feat(compiler): implement ternary conditional expressions from semantics to C codegen - #62

Merged
SuperIceCN merged 7 commits into
masterfrom
feat/frontend/ternary-expr
Aug 20, 2026
Merged

feat(compiler): implement ternary conditional expressions from semantics to C codegen#62
SuperIceCN merged 7 commits into
masterfrom
feat/frontend/ternary-expr

Conversation

@SuperIceCN

Copy link
Copy Markdown
Collaborator

Summary

Implement GDScript ternary conditional expressions (value1 if condition else value2, including right-nested forms) across the full pipeline: frontend shared semantics, compile gate lift, CFG construction, body lowering, and C backend execution.

What changed

  • Parser/AST baseline: froze parse shape and associativity tests for ConditionalExpression (FrontendConditionalParseBehaviorTest); no parser changes needed.
  • Shared semantics (FrontendExpressionSemanticSupport): ternary type merge contract — condition resolved with expectedType=null; both arms resolved against outer expectedType; merge via bidirectional determineFrontendBoundaryDecision, falling back to Variant; FAILED arm re-owns the root FrontendExpressionType (binary-style); void arm rejected at root, void + Variant degrades to DYNAMIC(Variant).
  • CFG construction (FrontendCfgGraphBuilder / FrontendCfgGraph): dual-context expansion — value-context ternaries produce branch-result MergeValueItem with anchor-typed slots; condition-context ternaries expand as pure control flow with zero merge items; BranchNode.conditionValueId must be a temp, never a merge id.
  • Merge contracts (MergeValueItem, FrontendBodyLoweringSupport, FrontendSequenceItemInsnLoweringProcessors): merge_write re-derives via materializeFrontendBoundaryValue; graph-wide merge-of-merge widening validation; merge slot lifetime identical to cfg_tmp_* (declare + __prepare__ default-construct + per-arm destroy-then-write + __finally__ destroy) so the backend needs no cfg_merge_ special cases.
  • Compile gate (FrontendCompileCheckAnalyzer): removed the explicit ConditionalExpression intercept; it now falls into the default compile-surface walk with exact-range dedup.
  • Docs: promoted the plan into the long-term fact source frontend_conditional_expression_implementation.md; synced 12 referencing docs and README support lists (ternary, plus for/lambda entries).
  • Tests/e2e: 9 ternary/ script+validation fixture pairs wired into GdScriptUnitTestCompileRunnerTest, covering same-type, int/float merge, nesting, object ancestor merge, null arm, non-bool condition, statement position, condition context, and destroyable arms.

Why

Ternary conditionals were the last common expression form explicitly blocked at the compile gate. Godot semantics (single-arm evaluation, type merge with boundary conversion) map naturally onto the existing branch-result merge infrastructure, so the implementation reuses MergeValueItem instead of adding backend-specific merge handling.

Affected packages/files

  • gd.script.gdcc.frontend.sema.analyzer (+ support): FrontendCompileCheckAnalyzer, FrontendExpressionSemanticSupport, FrontendBodyOwnerProcedures
  • gd.script.gdcc.frontend.lowering (+ cfg, cfg.item, pass.body): FrontendCfgGraphBuilder, FrontendCfgGraph, MergeValueItem, FrontendBodyLoweringSupport, FrontendSequenceItemInsnLoweringProcessors
  • Tests: parse behavior, sema support, compile gate, CFG builder/graph, lowering passes, e2e runner + ternary/ fixtures
  • Docs: doc/module_impl/frontend/* (13 files), README.md, README.zh-CN.md

Validation

  • .\gradlew.bat test --tests "gd.script.gdcc.frontend.*" --no-daemon --console=plain — all frontend tests green (1276)
  • pwsh -ExecutionPolicy Bypass -File script/run-gradle-targeted-tests.ps1 -Tests FrontendCompileCheckAnalyzerTest,FrontendCfgGraphBuilderTest,FrontendCfgGraphTest,FrontendLoweringBuildCfgPassTest,FrontendLoweringBodyInsnPassTest,GdScriptUnitTestCompileRunnerTest
  • .\gradlew.bat clean build --no-daemon --console=plain — 3207 tests; only 5 failures from a Windows DLL file-lock on unrelated engine tests (AccessDeniedException on construct_lambda_engine_debug_x86_64.dll), which pass when re-run individually
  • Generated ternary_destroyable_arms/entry.c inspected: merge slot declared, default-constructed in __prepare__, destroy-then-write per arm, destroyed in __finally__

Result: BUILD SUCCESSFUL (targeted); full build green except environment-only DLL lock flakes.

Risks / Notes

  • No constant folding or flow-sensitive type narrowing for ternary arms (matches Godot MVP scope).
  • No INCOMPATIBLE_TERNARY diagnostic; incompatible merges fall back to Variant like other binary merges.
  • Condition arm is never type-narrowed by the merge result; BranchNode.conditionValueId reuse of a merge id is forbidden by contract and tests.

Key behaviors covered

  • Right-associative nesting: a if c1 else b if c2 else c
  • Boundary conversion on mixed arms (int/float via c_int_to_float)
  • Object arms merged to common ancestor; null arm yields nullable/object-or-null merge
  • Non-bool conditions truthiness-evaluated; statement-position ternaries discard the merge value
  • Destroyable arm values get full ownership lifecycle through the merge slot

Diff stats

  • 54 files changed, 2956 insertions(+), 167 deletions(-)

Breaking changes

  • None

Related docs

  • doc/module_impl/frontend/frontend_conditional_expression_implementation.md
  • doc/module_impl/frontend/frontend_lowering_cfg_pass_implementation.md

…rnary operator

- Define scope and Godot-aligned semantics for ternary expressions with right-associative nesting
- Specify shared type inference and merged-type resolution with diagnostic ownership
- Outline CFG construction for value and condition contexts with merge contract refinements
- Detail staged implementation phases and testing matrix from parsing to end-to-end verification
- Anchor field mapping, right-associative nesting, and lowest-precedence binding at the parser boundary
- Cover parenthesized associativity overrides, statement-position usage, and tolerant incomplete-syntax handling
- Keep semantic analysis, CFG construction, and compile-gate surfaces unchanged for the baseline step
- Align implementation plan status and acceptance criteria with the landed characterization
…onal expressions

- Resolve ternary arms with contextual expected-type propagation and merge via the shared boundary compatibility matrix
- Handle runtime-open, void, and incompatible arm pairs with ordered fallback to dynamic or Variant semantics
- Promote conditional expressions from deferred to fully typed with right-associative nesting support
- Preserve diagnostic ownership so arm failures re-emit at the conditional root with precise source ranges
- Record phase completion in implementation plan and expand coverage for merging, contextuality, and error isolation
…owering

- Anchor merge slot types at the shared expression result instead of per-arm source values
- Route merge writes through the unified typed-boundary materialization as a re-derive consumer
- Allow merge-of-merge sources across sequences while keeping dangling and mixed sources rejected
- Update lowering and frontend rule documentation to reflect the refined contracts and Phase 2 completion
- Add coverage for anchor typing, nested merge sources, and fail-fast edges with and/or LIR shape preserved
…pressions

- Build value-context ternaries via shared branch-result merge with arm-private temps and merge-of-merge continuation
- Expand condition-context ternaries as pure control flow reusing short-circuit branching without merge values
- Preserve branch-local condition temps, anchor-typed merge slots, and preferred result identity for initializers
- Clarify lowering and frontend gate docs to mark CFG contracts complete with compile-gate still pending
- Expand coverage for nesting, associativity, short-circuit conditions/arms, and condition embeddings
… and complete integration validation

- Remove explicit compile block and route ternary expressions through normal type resolution and CFG lowering
- Expand focused coverage across compile checks, analysis passes, CFG shape, and body lowering for value, condition, and statement contexts
- Add end-to-end suites covering mixed-type merges, nested associativity, object and null merges, truthiness, and discard semantics
- Verify destroyable merge-slot lifecycle matches existing temporary and local slot ownership contracts
- Synchronize implementation plans and frontend rule documentation to reflect the completed surface
…cord

- Graduate conditional expression plan into maintained implementation documentation after full surface closure
- Retire transitional phase markers and interim compile-gate notes
- Synchronize cross-document references to the new implementation source of truth
Copilot AI lite review requested due to automatic review settings August 20, 2026 13:22

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 was unable to review this pull request because the user who requested the review has reached their quota limit.

@SuperIceCN
SuperIceCN merged commit 355b8f3 into master Aug 20, 2026
1 check passed
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