Skip to content

Conversation

@aytey
Copy link

@aytey aytey commented Nov 27, 2025

Overview

This PR introduces a callback-based unit provider that lets Python applications implement custom unit resolution logic without requiring GPR project files or predefined file lists.

The existing providers (for_project and auto) work well for standard Ada projects, but many applications need dynamic resolution based on runtime configuration, database lookups, virtual file systems, or non-standard naming conventions.

API

def my_resolver(name: str, kind: str) -> str | None:
    """
    Args:
        name: Unit name (e.g., "ada.text_io")
        kind: "spec" or "body"

    Returns:
        Path to source file, or None if not found
    """
    suffix = ".ads" if kind == "spec" else ".adb"
    return f"src/{name.replace('.', '-')}{suffix}"

provider = lal.UnitProvider.from_callback(my_resolver)
ctx = lal.AnalysisContext(unit_provider=provider)

Implementation

The implementation spans three layers:

  • Ada (libadalang-callback_provider.adb/ads) — implements Unit_Provider_Interface, manages callback invocation and memory ownership for returned filenames
  • C FFI (libadalang-implementation-c-extensions.adb/ads) — provides C-compatible callback types, handles charset conversion, documents memory contracts
  • Python (extensions/python_api/unit_providers/methods) — wraps C callbacks, handles malloc/free for string returns, retains callback references to prevent GC

Memory ownership: Python allocates filename strings with malloc(), Ada calls free() after copying. NULL returns indicate "unit not found".

Exceptions in callbacks are logged via _log_uncaught_error and the callback returns NULL, allowing analysis to continue.

Limitations

  • Assumes one compilation unit per file (PLE_Root_Index := 1)
  • The documentation example using a temporary context is limited to syntactic analysis

Testing

Tests cover basic resolution, None returns, exception handling, charset support (UTF-8 and default), multiple invocations, memory stress (110+ invocations), and Unicode unit names. All pass.

Documentation

The user manual now documents UnitProvider.from_callback() with a practical example, memory ownership contracts for C API users, and the limitations above.

This commit introduces a new callback-based unit provider that allows
Python applications to implement custom unit resolution logic entirely
in Python, without needing GPR project files or auto provider file lists.

The callback provider accepts a user-defined Python function that maps
(unit_name, unit_kind) pairs to file paths, enabling dynamic resolution
of Ada source files based on application-specific logic.

Key features:
- Simple Python callback interface with (name, kind) -> filename mapping
- Proper memory management across Python/Ada boundary using malloc/free
- Comprehensive error handling with graceful exception recovery
- Full UTF-8 support for unit names and file paths
- Consistent PLE_Root_Index behavior with existing unit providers
- Thread-safe callback reference management

Implementation includes:
- Ada implementation (callback_provider.adb/ads)
- C FFI bindings (implementation-c-extensions.adb/ads)
- Python API (unit_providers/methods, low_level_bindings)
- Comprehensive test suite with 7 test scenarios
- User manual documentation and examples

Signed-off-by: Andrew V. Teylu <andrew.teylu@vector.com>
@CLAassistant
Copy link

CLAassistant commented Nov 27, 2025

CLA assistant check
All committers have signed the CLA.

@aytey
Copy link
Author

aytey commented Nov 27, 2025

As a note, I tested this with the 26.0 branches -- I didn't actually test with master (because I build the "stable" branch); I just rebased my work from 26.0 onto master.

@pmderodat
Copy link
Member

Hello, thank you very much for your quality contribution.

This looks like a worthwhile addition to the Python API. There are several aspects that would need to be refined, but there is an important point to discuss first:

It looks like this should belong to Langkit directly, since there is nothing specific to Libadalang in this feature: all Langkit-generated libraries can have unit providers, so they all could benefit from the possibility of creating custom unit providers from Python. We (AdaCore) could take care of turning this PR into the corresponding Langkit patch, so there are two options:

  1. You take care of porting this PR to Langkit, in that case we’ll send a detailed review here first so that you can adjust the design while transitioning this to Langkit.
  2. Let us take care of the transition and the adjustments.

Please let us know how you would like to proceed.
Thank you,

@aytey
Copy link
Author

aytey commented Dec 26, 2025

Hello, thank you very much for your quality contribution.

Sorry for the delay in my response; I am out of the office for a prolonged Christmas break (I’m not back at work until Jan 12th).

so there are two options

Which do you prefer? I’m happy to do the work (as a learning experience), but also have no issue if you want to do it.

we’ll send a detailed review here first

Personally, if I am to do the work, I’d rather do a naive port langkit (of what I have now) and then solicit feedback on that.

@pmderodat let me know how you want to proceed.

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