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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions