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:
[]
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_64libadalang 26.0 on openSUSE.Reproducer
Output