Skip to content

Lambda function lowering #588

Merged
Roger-luo merged 3 commits intomainfrom
khwu/lambda_comp
Feb 25, 2026
Merged

Lambda function lowering #588
Roger-luo merged 3 commits intomainfrom
khwu/lambda_comp

Conversation

@kaihsin
Copy link
Collaborator

@kaihsin kaihsin commented Nov 25, 2025

This PR address #525

@kaihsin kaihsin requested a review from Roger-luo November 25, 2025 10:18
@github-actions
Copy link
Contributor

github-actions bot commented Nov 25, 2025

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
11784 10516 89% 0% 🟢

New Files

No new covered files...

Modified Files

File Coverage Status
src/kirin/dialects/lowering/func.py 95% 🟢
src/kirin/dialects/py/assign.py 87% 🟢
TOTAL 91% 🟢

updated for commit: 9253bc8 by action🐍

@github-actions
Copy link
Contributor

github-actions bot commented Nov 25, 2025

PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-02-25 15:32 UTC

@codecov
Copy link

codecov bot commented Nov 25, 2025

Codecov Report

❌ Patch coverage is 95.00000% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/kirin/dialects/lowering/func.py 95.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@kaihsin kaihsin changed the title Lambda Comp lowering Lambda function lowering Jan 6, 2026
@Roger-luo Roger-luo requested a review from Copilot February 25, 2026 15:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements lowering support for Python lambda functions to IR, addressing issue #525 which requested the ability to use inline lambda functions with ilist.map and other operations.

Changes:

  • Added lower_Lambda method to handle lambda AST nodes and convert them to func.Lambda IR statements with closure capture support
  • Added comprehensive test coverage for lambda functions including closures, nested lambdas, and integration with ilist.map
  • Minor whitespace formatting changes to assign.py

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
test/lowering/test_py_lambda.py New test file covering lambda lowering scenarios including closures, nested lambdas, and ilist.map integration
src/kirin/dialects/lowering/func.py Implementation of lower_Lambda method to convert Python lambda AST nodes to IR
src/kirin/dialects/py/assign.py Minor whitespace formatting changes (blank lines added)
Comments suppressed due to low confidence (5)

src/kirin/dialects/lowering/func.py:166

  • The state.frame context manager always creates a next_block (see state.py:192), so blocks[1] will exist. However, this deletion is fragile and relies on internal implementation details of state.frame. Consider adding a comment explaining that blocks[1] is the unused next_block created by state.frame, or checking that len(func_frame.curr_region.blocks) == 2 before deleting to make the code more robust.
        func_frame.curr_region.blocks[1].delete()

src/kirin/dialects/lowering/func.py:124

  • The output type is hardcoded to types.Any instead of trying to infer it from the lambda body. While this may be intentional for simplicity, it's inconsistent with how lower_FunctionDef handles the output type by calling self.get_hint(state, node.returns). Lambda nodes in Python AST don't have a returns attribute, so types.Any is the correct default, but consider adding a comment explaining this design choice.
            output=types.Any,

src/kirin/dialects/lowering/func.py:158

  • This commented-out assertion should either be removed or uncommented with proper error handling. If the assertion is valid (lambda should always have a return value), it should be active. If it's not valid, remove the comment. Leaving commented-out code creates confusion about the intended behavior.
            # assert hasattr(last_stmt,"result"), "python lambda should always have a return value"

src/kirin/dialects/lowering/func.py:179

  • There is significant code duplication between lower_Lambda and the nested function handling in lower_FunctionDef (lines 96-111). The callback function, entries setup, fn_self creation, and lambda statement construction are nearly identical. Consider extracting this shared logic into a helper method to improve maintainability and reduce the risk of inconsistencies when changes are made.
    def lower_Lambda(
        self, state: lowering.State[ast.AST], node: ast.Lambda
    ) -> lowering.Result:

        frame = state.current_frame
        slots = tuple(arg.arg for arg in node.args.args)
        self.assert_simple_arguments(node.args)
        signature = func.Signature(
            inputs=tuple(
                self.get_hint(state, arg.annotation) for arg in node.args.args
            ),
            output=types.Any,
        )
        node_name = f"lambda_0x{id(node)}"

        entries: dict[str, ir.SSAValue] = {}
        entr_block = ir.Block()
        fn_self = entr_block.args.append_from(
            types.MethodType[list(signature.inputs), signature.output],
            node_name + "_self",
        )
        entries[node_name] = fn_self
        for arg, type in zip(node.args.args, signature.inputs):
            entries[arg.arg] = entr_block.args.append_from(type, arg.arg)

        def callback(frame: lowering.Frame, value: ir.SSAValue):
            first_stmt = entr_block.first_stmt
            stmt = func.GetField(obj=fn_self, field=len(frame.captures) - 1)
            if value.name:
                stmt.result.name = value.name
            stmt.result.type = value.type
            stmt.source = state.source
            if first_stmt:
                stmt.insert_before(first_stmt)
            else:
                entr_block.stmts.append(stmt)
            return stmt.result

        with state.frame(
            [node.body], entr_block=entr_block, capture_callback=callback
        ) as func_frame:
            func_frame.defs.update(entries)
            func_frame.exhaust()

            last_stmt = func_frame.curr_region.blocks[0].last_stmt
            # assert hasattr(last_stmt,"result"), "python lambda should always have a return value"
            rtrn_stmt = func.Return(last_stmt)
            func_frame.curr_block.stmts.append(rtrn_stmt)

        first_stmt = func_frame.curr_region.blocks[0].first_stmt
        if first_stmt is None:
            raise lowering.BuildError("empty lambda body")

        func_frame.curr_region.blocks[1].delete()

        lambda_stmt = func.Lambda(
            tuple(value for value in func_frame.captures.values()),
            sym_name=node_name,
            slots=slots,
            signature=signature,
            body=func_frame.curr_region,
        )

        lambda_stmt.result.name = node_name
        frame.push(lambda_stmt)
        frame.defs[node_name] = lambda_stmt.result
        return lambda_stmt.result

src/kirin/dialects/lowering/func.py:159

  • While func.Return accepts both Statement and SSAValue arguments, passing last_stmt directly is inconsistent with the rest of the codebase. All other uses of func.Return in this file (lines 15, 19, 74) pass .result explicitly. For consistency and clarity, this should be func.Return(last_stmt.result). Additionally, there's no null check for last_stmt before accessing it, which could cause an AttributeError if the lambda body is empty (though this is checked later at line 163).
            rtrn_stmt = func.Return(last_stmt)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Roger-luo Roger-luo merged commit a2713b6 into main Feb 25, 2026
16 of 17 checks passed
@Roger-luo Roger-luo deleted the khwu/lambda_comp branch February 25, 2026 15:31
Roger-luo pushed a commit that referenced this pull request Feb 25, 2026
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