Skip to content

libadalang fails to resolve quoted predefined "xor" for modular types (infix works) #979

@aytey

Description

@aytey

Issue

For the script below, libadalang resolves infix modular xor (<modular> xor <modular>) but fails to resolve the quoted call "xor"(<modular>, <modular>) unless an explicit renaming is provided (e.g., function "xor" (Left, Right : Bitty) return Bitty is (Left xor Right);).

For Boolean operands, libadalang resolves the quoted "xor" without any renaming.

GNAT compiles both quoted and infix forms for modular types without an explicit renaming.

Is this a libadalang limitation (predefined modular operators not considered in quoted-call resolution), or am I misusing the API?

Environment: libadalang-25.0.0-9.50.x86_64 libadalang 26.0 on openSUSE.

Reproducer

#!/usr/bin/env python3
"""
Minimal LAL repro:
  - Boolean: quoted "xor" resolves (Standard."xor" is visible).
  - Modular: infix xor resolves, quoted "xor" does not (implicit predefined op).
  - Modular with explicit renaming: quoted "xor" resolves.

Ada sources (written to a temp dir):

--  test_types.ads
--  package Test_Types is
--     type Bitty is mod 256;
--     Left_Val  : constant Bitty := 45;
--     Right_Val : constant Bitty := 38;
--     Bool_L    : constant Boolean := True;
--     Bool_R    : constant Boolean := False;
--
--     -- Uncommented in the "with_renaming" variant below:
--     -- function "xor" (Left, Right : Bitty) return Bitty is (Left xor Right);
--  end Test_Types;
--
--  test_ops.adb
--  with Test_Types; use Test_Types;
--  procedure Test_Ops is
--     Infix_Result  : Bitty;
--     Quoted_Result : Bitty;
--     Bool_Infix    : Boolean;
--     Bool_Quoted   : Boolean;
--  begin
--     Infix_Result  := Left_Val xor Right_Val;
--     Quoted_Result := "xor" (Left_Val, Right_Val);
--     Bool_Infix    := Bool_L xor Bool_R;
--     Bool_Quoted   := "xor" (Bool_L, Bool_R);
--  end Test_Ops;
--
-- Expected behavior (GNAT): both compile.
-- Libadalang: infix resolves; quoted "xor" fails unless explicit renaming.
-- p_nameres_diagnostics shows LAL only trying Standard."xor" overloads.
--
-- Requires: pip install libadalang, GNAT in PATH for gnatls.
"""

import subprocess
import tempfile
from collections.abc import Sequence
from pathlib import Path

import libadalang as lal


def get_gnatls_search_paths() -> list[str]:
    """Return Ada include search paths from gnatls -v."""
    result = subprocess.run(
        ["gnatls", "-v"], capture_output=True, text=True, check=True, timeout=10
    )
    search_paths: list[str] = []
    for line in result.stdout.split("\n") + result.stderr.split("\n"):
        if "adainclude" in line.lower():
            p = Path(line.strip())
            if p.is_absolute() and p.exists():
                search_paths.append(str(p))
    return search_paths


def get_stdlib_files(search_paths: list[str]) -> list[str]:
    """Collect Ada stdlib .ads files from the given search paths."""
    stdlib_files: list[str] = []
    for sp in search_paths:
        for ext in ["*.ads", "*.ADS", "*.Ads"]:
            stdlib_files.extend(str(f) for f in Path(sp).glob(ext))
    return stdlib_files


def analyze_variant(label: str, with_renaming: bool) -> None:
    """Analyze a single variant (with or without renaming) and print results."""
    ada_types = """\
package Test_Types is
   type Bitty is mod 256;
   Left_Val  : constant Bitty := 45;
   Right_Val : constant Bitty := 38;
   Bool_L    : constant Boolean := True;
   Bool_R    : constant Boolean := False;
"""

    if with_renaming:
        ada_types += """\
   function "xor" (Left, Right : Bitty) return Bitty is (Left xor Right);
"""

    ada_types += """\
end Test_Types;
"""

    ada_body = """\
with Test_Types; use Test_Types;

procedure Test_Ops is
   Infix_Result  : Bitty;
   Quoted_Result : Bitty;
   Bool_Infix    : Boolean;
   Bool_Quoted   : Boolean;
begin
   Infix_Result  := Left_Val xor Right_Val;
   Quoted_Result := "xor" (Left_Val, Right_Val);
   Bool_Infix    := Bool_L xor Bool_R;
   Bool_Quoted   := "xor" (Bool_L, Bool_R);
end Test_Ops;
"""

    with tempfile.TemporaryDirectory() as tmpdir:
        tmp = Path(tmpdir)
        _ = (tmp / "test_types.ads").write_text(ada_types)
        _ = (tmp / "test_ops.adb").write_text(ada_body)

        search_paths = get_gnatls_search_paths()
        stdlib_files = get_stdlib_files(search_paths)
        project_files = [str(tmp / "test_types.ads"), str(tmp / "test_ops.adb")]

        ctx = lal.AnalysisContext(
            unit_provider=lal.UnitProvider.auto(
                input_files=project_files + stdlib_files
            )
        )
        unit = ctx.get_from_file(str(tmp / "test_ops.adb"))

        print(f"=== {label} ===")
        if unit.diagnostics:
            print("Parse diagnostics:", unit.diagnostics)

        infix_nodes: list[lal.BinOp] = []
        quoted_nodes: list[lal.CallExpr] = []
        for node in unit.root.findall(lambda _: True):
            if isinstance(node, lal.BinOp) and "xor" in node.text:
                infix_nodes.append(node)
            if isinstance(node, lal.CallExpr) and '"xor"' in node.text:
                quoted_nodes.append(node)

        def print_diagnostics(diags: Sequence[object]) -> None:
            """Print diagnostics as a list, always including [] when empty."""
            if diags:
                for d in diags:
                    print("   ", d)
            else:
                print("   []")

        print("Infix xor:")
        if not infix_nodes:
            print("  Not found")
        else:
            for binop in infix_nodes:
                print("  text:", binop.text)
                print("  expr_type:", binop.p_expression_type)
                print("  nameres_diagnostics:")
                print_diagnostics(binop.p_nameres_diagnostics)

        print('Quoted "xor":')
        if not quoted_nodes:
            print("  Not found")
        else:
            for call in quoted_nodes:
                print("  text:", call.text)
                print("  expr_type:", call.p_expression_type)
                print("  nameres_diagnostics:")
                print_diagnostics(call.p_nameres_diagnostics)

        print()


def main() -> None:
    """Run all variants and print results."""
    analyze_variant(
        "no renaming (Boolean resolves, modular quoted fails)", with_renaming=False
    )
    analyze_variant(
        "with explicit renaming (modular quoted resolves)", with_renaming=True
    )


if __name__ == "__main__":
    main()

Output

=== no renaming (Boolean resolves, modular quoted fails) ===
Infix xor:
  text: Left_Val xor Right_Val
  expr_type: <ConcreteTypeDecl ["Bitty"] test_types.ads:2:4-2:26>
  nameres_diagnostics:
   []
  text: Bool_L xor Bool_R
  expr_type: <ConcreteTypeDecl ["Boolean"] __standard:3:3-3:33>
  nameres_diagnostics:
   []
Quoted "xor":
  text: "xor" (Left_Val, Right_Val)
  expr_type: None
  nameres_diagnostics:
    <SolverDiagnostic message_template=expected {}, got {} args=[<ConcreteTypeDecl ["Wide_Wide_String"] __standard:109:3-110:44>, <ConcreteTypeDecl ["Bitty"] test_types.ads:2:4-2:26>] location=<Id "Left_Val" test_ops.adb:10:28-10:36> contexts=[<LogicContext ref_node=<Str ""xor"" test_ops.adb:10:21-10:26> decl_node=<SyntheticSubpDecl [""xor""] __standard:109:28-110:43>>] round=1>
    <SolverDiagnostic message_template=expected {}, got {} args=[<ConcreteTypeDecl ["Wide_Wide_String"] __standard:109:3-110:44>, <ConcreteTypeDecl ["Bitty"] test_types.ads:2:4-2:26>] location=<Id "Right_Val" test_ops.adb:10:38-10:47> contexts=[<LogicContext ref_node=<Str ""xor"" test_ops.adb:10:21-10:26> decl_node=<SyntheticSubpDecl [""xor""] __standard:109:28-110:43>>] round=1>
    <SolverDiagnostic message_template=expected {}, got {} args=[<ConcreteTypeDecl ["Wide_String"] __standard:107:3-108:39>, <ConcreteTypeDecl ["Bitty"] test_types.ads:2:4-2:26>] location=<Id "Left_Val" test_ops.adb:10:28-10:36> contexts=[<LogicContext ref_node=<Str ""xor"" test_ops.adb:10:21-10:26> decl_node=<SyntheticSubpDecl [""xor""] __standard:107:23-108:38>>] round=2>
    <SolverDiagnostic message_template=expected {}, got {} args=[<ConcreteTypeDecl ["Wide_String"] __standard:107:3-108:39>, <ConcreteTypeDecl ["Bitty"] test_types.ads:2:4-2:26>] location=<Id "Right_Val" test_ops.adb:10:38-10:47> contexts=[<LogicContext ref_node=<Str ""xor"" test_ops.adb:10:21-10:26> decl_node=<SyntheticSubpDecl [""xor""] __standard:107:23-108:38>>] round=2>
    <SolverDiagnostic message_template=expected {}, got {} args=[<ConcreteTypeDecl ["String"] __standard:105:3-105:57>, <ConcreteTypeDecl ["Bitty"] test_types.ads:2:4-2:26>] location=<Id "Left_Val" test_ops.adb:10:28-10:36> contexts=[<LogicContext ref_node=<Str ""xor"" test_ops.adb:10:21-10:26> decl_node=<SyntheticSubpDecl [""xor""] __standard:105:18-105:56>>] round=3>
    <SolverDiagnostic message_template=expected {}, got {} args=[<ConcreteTypeDecl ["String"] __standard:105:3-105:57>, <ConcreteTypeDecl ["Bitty"] test_types.ads:2:4-2:26>] location=<Id "Right_Val" test_ops.adb:10:38-10:47> contexts=[<LogicContext ref_node=<Str ""xor"" test_ops.adb:10:21-10:26> decl_node=<SyntheticSubpDecl [""xor""] __standard:105:18-105:56>>] round=3>
    <SolverDiagnostic message_template=expected {}, got {} args=[<ConcreteTypeDecl ["Boolean"] __standard:3:3-3:33>, <ConcreteTypeDecl ["Bitty"] test_types.ads:2:4-2:26>] location=<Id "Left_Val" test_ops.adb:10:28-10:36> contexts=[<LogicContext ref_node=<Str ""xor"" test_ops.adb:10:21-10:26> decl_node=<SyntheticSubpDecl [""xor""] __standard:3:19-3:32>>] round=4>
    <SolverDiagnostic message_template=expected {}, got {} args=[<ConcreteTypeDecl ["Boolean"] __standard:3:3-3:33>, <ConcreteTypeDecl ["Bitty"] test_types.ads:2:4-2:26>] location=<Id "Right_Val" test_ops.adb:10:38-10:47> contexts=[<LogicContext ref_node=<Str ""xor"" test_ops.adb:10:21-10:26> decl_node=<SyntheticSubpDecl [""xor""] __standard:3:19-3:32>>] round=4>
  text: "xor" (Bool_L, Bool_R)
  expr_type: <ConcreteTypeDecl ["Boolean"] __standard:3:3-3:33>
  nameres_diagnostics:
   []

=== with explicit renaming (modular quoted resolves) ===
Infix xor:
  text: Left_Val xor Right_Val
  expr_type: <ConcreteTypeDecl ["Bitty"] test_types.ads:2:4-2:26>
  nameres_diagnostics:
   []
  text: Bool_L xor Bool_R
  expr_type: <ConcreteTypeDecl ["Boolean"] __standard:3:3-3:33>
  nameres_diagnostics:
   []
Quoted "xor":
  text: "xor" (Left_Val, Right_Val)
  expr_type: <ConcreteTypeDecl ["Bitty"] test_types.ads:2:4-2:26>
  nameres_diagnostics:
   []
  text: "xor" (Bool_L, Bool_R)
  expr_type: <ConcreteTypeDecl ["Boolean"] __standard:3:3-3:33>
  nameres_diagnostics:
   []

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions