Skip to content

Commit 3b5d4e8

Browse files
Add FuncSortRef and fix QuantifierRef.sort for lambdas (#118)
QuantifierRef.sort() hardcoded the Boolean sort, so a lambda expression reported Bool instead of its actual function sort. Return the real sort for lambdas, and introduce FuncSortRef so that function sorts are more than an opaque SortRef. _to_sort_ref now routes function sorts to it, giving every function sort - a lambda's or an uninterpreted function's - the arity(), domain(), domain_n() and range() accessors. Those names mirror Z3Py's ArraySortRef, where lambdas have array sorts, so code inspecting a lambda's sort carries over from Z3Py. cvc5 keeps function and array sorts distinct, so is_array_sort() still reports False for them; add is_func_sort() to test for the new sort. FuncDeclRef.arity/domain/range now delegate to the sort instead of each re-deriving from getSort().getFunction*(), which is behavior-preserving: every FuncDeclRef is built from a function sort. This supersedes #115. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 4c0ce75 commit 3b5d4e8

1 file changed

Lines changed: 92 additions & 4 deletions

File tree

cvc5_pythonic_api/cvc5_pythonic.py

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,8 @@ def _to_sort_ref(s, ctx):
793793
return FPSortRef(s, ctx)
794794
elif s.isRoundingMode():
795795
return FPRMSortRef(s, ctx)
796+
elif s.isFunction():
797+
return FuncSortRef(s, ctx)
796798
return SortRef(s, ctx)
797799

798800

@@ -803,6 +805,75 @@ def _to_sort_ref(s, ctx):
803805
#########################################
804806

805807

808+
class FuncSortRef(SortRef):
809+
"""Function sorts.
810+
811+
The sort of an uninterpreted function or of a lambda expression: it maps a
812+
tuple of domain sorts to a range sort.
813+
814+
>>> Function('f', IntSort(), RealSort(), BoolSort()).sort()
815+
(-> Int Real Bool)
816+
817+
Z3Py models lambdas as arrays, so its lambda sorts are array sorts. cvc5
818+
keeps function and array sorts distinct, but this class offers the same
819+
accessors as `ArraySortRef` so that code written against Z3Py's array
820+
sorts carries over.
821+
"""
822+
823+
def arity(self):
824+
"""Return the number of arguments of the function sort `self`.
825+
826+
>>> f = Function('f', IntSort(), RealSort(), BoolSort())
827+
>>> f.sort().arity()
828+
2
829+
"""
830+
return self.ast.getFunctionArity()
831+
832+
def domain(self):
833+
"""Return the first domain of the function sort `self`.
834+
835+
Use `domain_n` to reach the domains of a function of arity two or more.
836+
837+
>>> f = Function('f', IntSort(), RealSort(), BoolSort())
838+
>>> f.sort().domain()
839+
Int
840+
"""
841+
return self.domain_n(0)
842+
843+
def domain_n(self, i):
844+
"""Return the sort of the argument `i` of the function sort `self`.
845+
This method assumes that `0 <= i < self.arity()`.
846+
847+
>>> f = Function('f', IntSort(), RealSort(), BoolSort())
848+
>>> f.sort().domain_n(0)
849+
Int
850+
>>> f.sort().domain_n(1)
851+
Real
852+
"""
853+
return _to_sort_ref(self.ast.getFunctionDomainSorts()[i], self.ctx)
854+
855+
def range(self):
856+
"""Return the range of the function sort `self`.
857+
858+
>>> f = Function('f', IntSort(), RealSort(), BoolSort())
859+
>>> f.sort().range()
860+
Bool
861+
"""
862+
return _to_sort_ref(self.ast.getFunctionCodomainSort(), self.ctx)
863+
864+
865+
def is_func_sort(s):
866+
"""Is this a function sort?
867+
868+
>>> is_func_sort(Function('f', IntSort(), BoolSort()).sort())
869+
True
870+
>>> is_func_sort(ArraySort(IntSort(), BoolSort()))
871+
False
872+
"""
873+
instance_check(s, SortRef)
874+
return s.ast.isFunction()
875+
876+
806877
class FuncDeclRef(ExprRef):
807878
"""Function declaration.
808879
Every constant and function have an associated declaration.
@@ -831,7 +902,8 @@ def arity(self):
831902
>>> f.arity()
832903
2
833904
"""
834-
return self.ast.getSort().getFunctionArity()
905+
# safe b/c a declaration always has a function sort
906+
return self.sort().arity() # type: ignore
835907

836908
def domain(self, i):
837909
"""Return the sort of the argument `i` of a function declaration.
@@ -843,7 +915,8 @@ def domain(self, i):
843915
>>> f.domain(1)
844916
Real
845917
"""
846-
return _to_sort_ref(self.ast.getSort().getFunctionDomainSorts()[i], self.ctx)
918+
# safe b/c a declaration always has a function sort
919+
return self.sort().domain_n(i) # type: ignore
847920

848921
def range(self):
849922
"""Return the sort of the range of a function declaration.
@@ -853,7 +926,8 @@ def range(self):
853926
>>> f.range()
854927
Bool
855928
"""
856-
return _to_sort_ref(self.ast.getSort().getFunctionCodomainSort(), self.ctx)
929+
# safe b/c a declaration always has a function sort
930+
return self.sort().range() # type: ignore
857931

858932
def __call__(self, *args):
859933
"""Create an SMT application expression using the function `self`,
@@ -9050,7 +9124,21 @@ def as_ast(self):
90509124
return self.ast
90519125

90529126
def sort(self):
9053-
"""Return the Boolean sort"""
9127+
"""Return the Boolean sort, or the function sort of a lambda.
9128+
9129+
>>> f = Function('f', IntSort(), IntSort())
9130+
>>> x, y = Ints('x y')
9131+
>>> ForAll(x, f(x) == 0).sort()
9132+
Bool
9133+
>>> Lambda(x, f(x)).sort()
9134+
(-> Int Int)
9135+
>>> Lambda(x, f(x)).sort().domain()
9136+
Int
9137+
>>> Lambda([x, y], f(x) + y).sort().range()
9138+
Int
9139+
"""
9140+
if self.is_lambda():
9141+
return _sort(self.ctx, self.as_ast())
90549142
return BoolSort(self.ctx)
90559143

90569144
def is_forall(self):

0 commit comments

Comments
 (0)