feat(compiler): implement GDScript lambda expressions from semantics to C codegen - #60
Merged
Conversation
- Track ordered lambda captures with self and nested-scope handling - Publish validated lambda plans through frontend analysis data - Serialize capture metadata into LIR and generate compatible C function headers - Add coverage for capture planning, side-table stability, serialization, and templates - Document lambda implementation and diagnostic requirements
- Bind lambda parameters, locals, and captures with placeholder types - Plan nested capture propagation and instance self captures - Preserve fail-closed handling for unsupported match and const subtrees - Add diagnostics, tests, and implementation documentation
- Resolve lambda-local values, captures, and nested suite ownership - Add transactional resolution patches with fail-closed diagnostics - Improve visible-value and lexical environment handling - Expand semantic analysis and lambda resolution test coverage - Update frontend implementation documentation
- Publish Callable types for recorded lambda expressions - Type-check nested lambda bodies with inherited callable context - Preserve Variant inference for silent lambda initializers - Add compile-time blockers until lambda lowering is implemented - Expand semantic analysis tests and implementation documentation
- Materialize hidden static lambda functions with typed parameters, returns, and captures - Lower nested lambda bodies through the executable CFG and instruction pipeline - Propagate declared lambda return types through semantic analysis and lowering - Reserve the synthetic `_lambda_` namespace and fail fast on missing plans or collisions - Expand frontend tests and implementation documentation
- Lower recorded lambda expressions into construct operations - Preserve local, self, and nested capture bindings - Validate synthesized lambda metadata during instruction lowering - Thread result value identities through CFG construction - Expand frontend tests and implementation documentation
…port - Generate lambda Callable callbacks with capture storage and lifecycle handling - Preserve captured values and self object identity across lambda invocation - Add backend, frontend, and integration coverage for lambda construction - Update lambda implementation and runtime documentation
- Finalize lambda semantic analysis, capture propagation, and lowering across nested scopes - Add compile-time diagnostics for invalid and unsupported lambda constructs - Align Callable construction and generated capture handling with backend runtime requirements - Expand unit and integration coverage for lambdas and signal connections - Consolidate frontend lambda documentation and implementation tracking
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implement GDScript
func(...)lambda expressions end-to-end across frontend, LIR, C backend, and runtime helpers. Recorded lambdas in function/constructor bodies now plan captures, synthesize hidden_lambda_<k>shells, lower toconstruct_lambda, and materialize custom Callables. Property-initializer, parameter-default,match, andawaitlambda surfaces remain fail-closed.What changed
FrontendVariableAnalyzer.bindLambdaInventorybinds lambda parameters, locals, andCAPTUREslots.FrontendLambdaCapturePlannerderives ordered captures (outerPARAMETER/LOCAL/CAPTURE, plus leadingselfwhen needed) without capturing class/global/utility names.FrontendSuiteResolverpublishes the first completeFrontendLambdaPlanafter filling declaration-site capture types and the declared return type. Recorded lambdas type asRESOLVED(GdCallableType);FrontendTypeCheckAnalyzertype-checks lambda bodies as independent callable islands.FrontendCompileCheckAnalyzerreleases recorded lambdas (published plan + published body) onto the compile surface and recursively scans body facts. Unrecorded lambdas stay fail-closed.FrontendLoweringFunctionPreparationPasssynthesizes hidden static_lambda_<k>shells (is_lambda+is_hidden+is_static). CFG emitsLambdaConstructItem; body lowering materializesConstructLambdaInsnfrom the frozen plan only.construct_lambda "<name>" $capture....LirFunctionDefpreserves capture insertion order;DomLirSerializerwrites real<capture name type>entries.ConstructInsnGen.emitConstructLambdacopies captures into a heap${Class}_Capture_${func}block and callsgdcc_new_lambda_callable.CCodegencopies_capture->nameinto matching locals before__prepare__and excludes capture locals from default construction.entry.h.ftlgenerates call / is_valid / free / get_argument_count wrappers.frontend_lambda_implementation.mdas the single fact source and retarget LIR, runtime, compile-gate, lowering, signal, and test-suite docs.script/lambda/andvalidation/lambda/, includingsignal_connect_lambda.gd.Why
Signal/Callableconsumers.FrontendLambdaPlanper recorded lambda and consume it downstream instead of re-deriving captures. Use a singleconstruct_lambdaopcode withselfas a leading capture, rather than addingOPCODE_CREATE_SELF_LAMBDAor reusingconstruct_callable/construct_standalone_callable.:=stabilization), not later assertions. Captures are construction-time copies, matching Godot user-visible semantics while using a userdata ABI.Affected packages/files
gd.script.gdcc.frontend.sema.**(FrontendLambdaPlan,FrontendLambdaCapturePlanner,FrontendSuiteResolver,FrontendVariableAnalyzer,FrontendCompileCheckAnalyzer,FrontendTypeCheckAnalyzer)gd.script.gdcc.frontend.lowering.**(FrontendLoweringFunctionPreparationPass,LambdaConstructItem,FrontendLambdaConstructInsnLoweringProcessor)gd.script.gdcc.lir.**(ConstructLambdaInsn,LirFunctionDef,DomLirSerializer)gd.script.gdcc.backend.c.gen.**(ConstructInsnGen,CCodegen,CGenHelper)src/main/c/codegen/include_451/gdcc/gdcc_callable.h,src/main/c/codegen/template_451/entry.h.ftl,func.ftldoc/module_impl/frontend/frontend_lambda_implementation.md,doc/gdcc_low_ir.md,doc/gdcc_runtime_lib.mdsrc/test/resources/**/script/lambda/andvalidation/lambda/Validation
pwsh -ExecutionPolicy Bypass -File script/run-gradle-targeted-tests.ps1 -Tests FrontendLambdaCapturePlannerTest,FrontendLambdaSuiteResolutionTest,FrontendLambdaExpressionTypeTest,FrontendLambdaInventoryTest,FrontendLambdaPlanSideTableTest,FrontendLambdaLoweringTest,ConstructLambdaInsnGenTest,FuncHeaderCaptureTemplateTest,FrontendCompileCheckAnalyzerTest,FrontendTypeCheckAnalyzerTestResult:
BUILD SUCCESSFUL(174 tests: 12 capture-planner, 11 suite-resolution, 2 expression-type, 2 inventory, 3 plan side-table, 14 lowering, 10 construct-codegen, 4 capture-template, 67 compile-check, 49 type-check; 0 failures, 0 skipped)Risks / Notes
match/ block-localconst/awaitinside lambda bodies;CAPTUREas a direct-slot alias root;Callable.bind/unbind; ClassDB registration of hidden lambda functions; compiler-only types on lambda params/returns/captures.construct_lambdais added._lambda_is a reserved compiler-owned prefix (sema.class_skeleton+ skip).GDScriptLambdaSelfCallable;object_idis the cached fat-pointerinstance_idonly whencapturesSelf, otherwise0. Captures travel in a userdata struct plus a trailing_capturepointer, not as prefixed Godot call arguments. Hash/equal stay Godot's default (call func + userdata identity).InvalidInsnException); source errors stay onDiagnosticManager+ skip.ConstructLambdaInsnGenEngineTest) are environment-aware and skip gracefully when Zig /GODOT_BINis unavailable.Key behaviors covered (Optional)
selfis a leading capture of the enclosing class object type.Signal.connect(func(): self.foo())uses ObjectDB-checkedis_valid_funcso the connection invalidates after instance free.get_argument_count.__prepare__default construction, then copied from_capture->namein the prologue.var cb := func(): ...slots toCallable.Diff stats (Optional)
Breaking changes (Optional)
Related docs (Optional)
doc/module_impl/frontend/frontend_lambda_implementation.mddoc/module_impl/frontend/frontend_rules.mddoc/gdcc_low_ir.mddoc/gdcc_runtime_lib.mddoc/module_impl/frontend/frontend_signal_support.md