Attach Refine(lambda ...) to a type with
Annotated. The first binder receives the constrained value.
Multiple predicates are conjoined.
from refinepy import Refine
from typing import Annotated
Nat = Annotated[int, Refine(lambda v: v >= 0)]
def increment(x: Nat) -> Annotated[int, Refine(lambda result, x: result == x + 1)]:
return x + 1from collections.abc import Sequence
def at(
xs: Sequence[int],
i: Annotated[int, Refine(lambda v, xs: 0 <= v < len(xs))],
) -> int:
return xs[i]Additional binders name entry inputs: parameter contracts can refer to earlier parameters; return contracts can refer to any parameter. Reassigning an input in the body does not change its entry value in the contract.
Aliases and local declaration contracts use unary predicates. A local
contract such as count: Nat = 0 applies to later
assignments; a parameter contract applies only at entry. All parameters
and returns require types; local types can be inferred.
Literal restricts a value to a nonempty set of same-base
int, bool, str or
None literals. Signed integers, static type aliases and
quoted annotations are supported.
from refinepy import Refine
from typing import Literal
Mode = Literal["read", "write"]
def switch(mode: Mode) -> Mode:
return "write" if mode == "read" else "read"
def reverse_pair(pair: tuple[Literal[0, 1], Mode]) -> tuple[Mode, Literal[0, 1]]:
return pair[::-1]Mixed bases, computed/named values, enums and nested
Literal are unsupported. True satisfies
Literal[1]; integer 1 does not satisfy
Literal[True]. Defaults are base-checked at definition and
refinement-checked when used.
Fixed tuples support construction, literal indexing, concatenation, equality and static slicing with a nonzero step. Slice bounds must be static or omitted/None. Every field is evaluated and checked, even for an empty slice or a comparison whose result is already known. Equality compares fields recursively; list fields need matching comparable element sorts. Mapping and abstract-sequence field equality are unsupported. Different arities or unrelated scalar types are unequal.
Ordering is lexicographic over compatible integer/bool, string, nested-tuple or matching None fields, then arity. Optional and container common fields are rejected. Tuples can contain nested supported values, but preserve aliases: storing a written list in a tuple or extracting a list field does not establish writable ownership.
@requires(lambda ...) constrains entry inputs, including
later parameters. Multiple preconditions are conjoined.
from refinepy import requires
@requires(lambda xs, i: 0 <= i < len(xs))
def read_at(xs: Sequence[int], i: int) -> int:
return xs[i]requires, ensures and
decreases each take one positional literal lambda with
ordinary required positional binders. Imports, aliases and re-exports
work; same-named user functions do not. @reflect takes no
parentheses. Duplicate reflect or decreases
markers are errors; decorator order has no effect. Imports must be
available before the definition, outside TYPE_CHECKING.
Other decorators and old function-comment directives are
unsupported.
Calls must satisfy parameter contracts and preconditions. Source callees are checked before their return contracts can be used. The companion decorators do not execute predicates or enforce contracts at runtime; the verifier does not import the checked code.
@ensures adds a return predicate while retaining the
required base return type. The first binder receives the result; others
name immutable entry inputs. For -> None, omit the
result binder; lambda: proposition is also allowed.
from refinepy import ensures
@ensures(lambda result, n: result == n + 1)
def increment(n: int) -> int:
return n + 1
@ensures(lambda n: n + 0 == n)
def identity_lemma(n: int) -> None:
return NoneMultiple ensures decorators and return
Refine metadata are conjoined. Each predicate must
independently be safe to evaluate; it cannot justify its own or another
predicate’s definedness. Reflected definitions require plain return
contracts and cannot use ensures.
Use one @decreases(lambda ...) on a directly recursive
function. Binders name entry inputs; the measure is an integer or a
literal nonempty flat tuple of plain integers. Each self-call must
establish definedness and strict descent, with all current/next
components nonnegative. For a scalar:
0 <= next < entry. Tuple descent is lexicographic;
later components may reset after an earlier decrease. Base cases need
not have nonnegative measures. Boolean components are rejected.
A decreases(lambda ...) call immediately before a loop
uses current bound header locals. Every normal or continue
back edge must establish a defined, nonnegative, strictly smaller
measure, including the final iteration. break, return and
zero iterations need no decrease. Every nested loop needs a measure; an
unbound for target or hidden iterator counter cannot be a
binder.
All callees must independently terminate. Descent is checked without call-result contracts, so result-dependent descent may remain unproved. Trusted external models and mutual recursion cannot establish termination. A requested proof requires measures on every loop, including unreachable loops.
verified alone establishes normal-return contracts.
Termination needs evidence for the selected function and its checked
calls. It does not guarantee Python recursion-depth safety or available
resources.
Assert checks a proposition at a source position without
imposing a contract on later assignments. The target must be a
definitely bound function local; the unquoted annotation has no
initializer and exactly one Assert item.
from typing import Annotated
from refinepy import Assert
def absolute(n: int) -> int:
x = abs(n)
x: Annotated[int, Assert(lambda value: value >= 0)]
return xThe base type must match the current value; existing refinements are checked too. The first lambda binder receives that value; additional binders name current bound locals. Free local captures, defaults and variadic binders are unsupported. Later rebinding does not update the established fact.
Assert(lambda v: P(v), using=lambda arg: lemma(arg))
uses one direct refined-None source lemma call. Arguments and
preconditions are checked first; the complete lemma closure must
independently terminate. External models, loops, mutual recursion and
circular assertion evidence cannot supply this static proof. Without
using, the proposition must follow from current facts and
admitted reflected equations. Python does not evaluate these local
annotations. Assert cannot appear in parameters, returns,
aliases, nested types or module annotations.
@reflect makes a checked definition callable in
predicates. Inputs and results must have plain supported types, without
nested refinements, preconditions or ensures. Assignments,
flat unpacking, assertions, branches and returns are supported; loops
and container writes are not. The original body and every argument
evaluation must be safe, even when a computation is discarded.
A lemma is a checked function whose return contract states a
proposition, often
Annotated[None, Refine(lambda _, x: ...)] or
@ensures on -> None. Its body must prove
that proposition. Calling it supplies the normal-return contract.
Ordinary contracts alone do not make a function callable in logic. See
the Guide
example.
A reflected definition may call itself by its declaration name with
plain supported input/result types, a loop-free body, no preconditions
and an explicit integer decreases measure. Raw-body safety
and descent must verify before its equations can be used; every other
callee must also verify and terminate.
Unfolding is bounded: eight layers for scalar-only signatures, three
for signatures containing containers. Remaining calls are uninterpreted.
Queries that cannot resolve them may return unknown,
including some false equations; expansion limits can also prevent
admission or proof. Mutual recursion, aliased self-calls and recursive
signatures with composite container elements are excluded. Use a checked
inductive lemma for equations beyond bounded unfolding.
A directly recursive function with a refined None result
and an integer or lexicographic decreases measure can prove
its return proposition by induction. Every self-call must satisfy its
contracts and strictly descend; its declared return proposition is the
local induction hypothesis. All base and recursive returns must prove
the proposition. Descent is independently checked without call-result
summaries; dependencies must verify and terminate, and loops need
measures. Reflected definitions are admitted separately.
There is no automatic induction search or mutual induction. Without a measure, a lemma establishes normal-return partial correctness only.
Element contracts apply to every valid element; outer contracts
constrain the container. Reads require bounds or key-presence proofs.
Negative sequence indices are supported. T | None and
Optional[T] support absence checks and narrowing.
NonEmptyNats = Annotated[Sequence[Nat], Refine(lambda xs: len(xs) > 0)]
def first(xs: NonEmptyNats) -> Nat:
return xs[0]from collections.abc import Mapping
def port_or_zero(ports: Mapping[str, Nat]) -> Nat:
port = ports.get("http")
if port is None:
return 0
return portFresh unshared local lists permit standalone
append/extend, += and integer
item assignment. Element and whole-list refinements remain checked.
Input/shared lists, live iterators, pop, slice assignment
and dictionary writes are unsupported.
A checked source helper returning a fresh list can transfer writable
ownership under one local name. A helper returning a tuple of
independently allocated, disjoint mutable containers can transfer lists
through direct flat unpacking: left, right = split(xs).
Borrowed or shared fields, saving the tuple before unpacking, and nested
unpacking cannot establish that ownership. Equal-content contracts alone
are insufficient; every dependency and return alternative must verify.
Abstract Sequence and optional results do not transfer ownership.
Container elements may be supported products, optionals or nested containers. Owning an outer list does not make extracted inner lists writable. Storing another written local list as an element is rejected. List equality accepts matching recursively comparable scalar, optional, tuple and list element sorts, even with different refinements; abstract Sequence and mapping elements block it. Concatenation requires matching element contracts, and writable container types remain invariant. See the Guide.
Place invariant(lambda ...) calls directly before the
loop when inference cannot establish the needed property. Binders name
current header locals. Add one decreases(lambda ...) to
request termination.
from refinepy import invariant, decreases
def count_to(n: Nat) -> Annotated[int, Refine(lambda result, n: result == n)]:
i = 0
invariant(lambda i, n: 0 <= i <= n)
decreases(lambda i, n: n - i)
while i < n:
i += 1
return iEach marker takes one positional literal lambda with required binders. Consecutive markers attach to the next loop in the same block; comments and blank lines may intervene, other statements may not. Imports, aliases and re-exports work. Markers do not execute their predicates at runtime. Legacy invariant comments remain accepted but are deprecated.
The invariant is checked initially and after every iteration. An
assert is also a proof obligation and must use the
supported predicate language.
Admission does not guarantee a proof for every property. Unsupported forms, solver uncertainty or exhausted analysis/model-reconstruction limits cannot produce a successful verification result.
| Area | Supported writing patterns | Unsupported or restricted forms |
|---|---|---|
| Scalar types | int, bool, str,
None |
No float, complex, bytes,
Any, object or user-defined classes |
| Optional and tuple types | T \| None, Optional[T], fixed tuples of
supported value fields, single-base Literal contracts |
No general unions or variable-length tuple annotations |
| Container types | Sequence[T], list[T],
Mapping[str, T], dict[str, T]; T
is a supported value type, optionally refined |
No sets, arbitrary mapping keys, custom container implementations or writes through extracted nested aliases |
| Arithmetic | +, -, * (including
a * b and a * a), comparisons; //
and % with proved nonzero integer divisors |
No /, powers or bitwise operators; variable-divisor and
nonlinear obligations may return unknown |
| Conditions | if/elif/else, chained
comparisons, and/or, conditional expressions,
is None narrowing |
Operand types must have a supported join; comparison chains
containing None, in or not in
require separate boolean guards; no mapping truth tests or arbitrary
identity tests |
| Strings | Content equality, inequality, lexicographic ordering,
len, concatenation, safe integer indexing and step-1
slicing in bodies/predicates |
No other slice steps, iteration, f-strings or string methods |
| Sequence operations | Length, integer indexing, stable iteration; list equality and concatenation; list slicing in bodies/predicates with a nonzero constant step | List equality requires matching recursively comparable element
sorts; concatenation also requires matching element contracts; no
abstract Sequence equality, abstract Sequence
slicing, membership tests or input/shared-list mutation |
| Fixed-tuple operations | Construction, length, literal indexing, equality/inequality, lexicographic ordering, concatenation and static slicing in bodies/predicates | Comparable common fields required for ordering; no dynamic product slice bounds |
| Mapping operations | String-key membership, lookup and
get(key[, default]) |
No mapping length, iteration,
keys/values/items or updates |
| Assignment | Local rebinding, chained assignment, flat fixed-tuple unpacking;
integer augmented assignment and local string +=; exclusive
local-list append/extend/+= and integer writes |
No input/shared-list, slice or attribute writes, nested/starred
unpacking, del, global or
nonlocal |
| Iteration | while; for over modeled sequences, direct
range or direct enumerate; loop
else, break, continue |
No general iterator values, zip, iter,
next, generators or async iteration |
| List comprehensions | One synchronous for, a name target and a pure
supported-value element expression over a modeled sequence or
homogeneous fixed tuple |
Nested construction and comprehensions are supported; no filters, multiple generators in one comprehension or unreflected source calls in the element expression |
| Functions | Top-level annotated functions, fixed positional/keyword arguments,
/, keyword-only parameters and scalar/None
literal defaults or flat tuples of
int/bool/str literals; direct
recursion with declared contracts |
No nested-tuple defaults or tuple defaults containing
None; no mutual recursion, closures, other decorators,
function values, variadic arguments or argument expansion |
| Generics | One constrained scalar parameter: T: (int, str) or
T: (bool, str), also expressible with
TypeVar |
No overlapping int/bool pair,
unconstrained/bounds-only generics, multiple parameters or generic
aliases |
| Other statements | return, pass, proof assertions |
No match, raise, try,
with, yield or async functions |
Predicates must return bool and be safe under the declared types and
established facts. Short-circuit guards can justify reads:
len(xs) > 0 and xs[0] >= 0.
| Expression | Admitted use |
|---|---|
| Literals, lambda binders, immutable scalar constants | No arbitrary global reads or assignments |
| Integer arithmetic and comparisons | Variable multiplication and division/modulo are admitted; divisor nonzero must follow from earlier short-circuit guards or entry facts |
and, or, not, conditional
expressions |
Supported truth conversion and compatible value types; final predicate is boolean |
len(value) |
Strings, modeled sequences and fixed tuples; not mappings |
xs[i], mapping[key],
key in mapping |
Prove bounds or key presence before the read; fixed-tuple field indices are literals |
mapping.get(key[, default]) |
String keys and a compatible scalar/optional default |
abs(x), min(x, y),
max(x, y) |
Integer arguments; min/max take exactly
two positional arguments |
List literals, equality, concatenation and constant-step slices are
allowed; empty lists need a contextual element type. Tuple construction,
concatenation and static slicing are allowed too, with every field
checked for safety. Unreflected user calls, explicit quantifiers,
mapping construction and comprehensions are not allowed in predicates.
sum, all, any,
sorted, isinstance, scalar conversions and
third-party APIs have no default model.
A verified contract applies to inputs satisfying entry contracts and
to the modeled built-in runtime. It checks modeled runtime errors and
normal returns; termination is separate evidence. Python annotations do
not enforce contracts. The browser reads one module without executing
Python; package resolution and external model registration use the
native workflow. Page navigation supports #/playground,
#/guide, #/reference and their section links.
Samples use #/playground/<sample> with
bounds, fibonacci, squareRoot,
series, rounding or division.
Needs review groups unproved,
unknown and unsupported;
Failed groups counterexample and
error. Individual statuses remain visible. Report-level
diagnostics prevent an all-verified summary. See Guide for status meanings and
termination summaries.