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.

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.

2 participants