Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
86f90bd
Implementing initial spec transformer
jyoo980 Feb 14, 2026
c3249c8
Avoid removing preconditions when no `Assigns` clause is present
jyoo980 Feb 14, 2026
f917b98
Implementing `move_preconditions_to_ensures`
jyoo980 Feb 15, 2026
c31a153
Addressing CodeRabbit comments
jyoo980 Feb 15, 2026
1dc0a61
Removing debug statement
jyoo980 Feb 15, 2026
8dcfc9e
Merge branch 'main' into yoo/spec-transformer
jyoo980 Feb 16, 2026
6e19314
Making `negate()` a method defined on `CBMCAst`
jyoo980 Feb 19, 2026
d58429c
Applying some fixes
jyoo980 Feb 19, 2026
15e067d
Tweak comments
mernst Feb 20, 2026
ab94719
Merge branch 'yoo/spec-transformer' of github.com:vikramnitin9/rust_v…
mernst Feb 20, 2026
d533dce
Addressing more CodeRabbit comments
jyoo980 Feb 22, 2026
fd24975
Merge branch 'yoo/spec-transformer' of github.com:vikramnitin9/rust_v…
jyoo980 Feb 22, 2026
ecc8b0f
Creating explicit variant generation module
jyoo980 Feb 22, 2026
4198627
Adding transformation to remove preconditions
jyoo980 Feb 22, 2026
6311651
Adding scratch work for precondition creation
jyoo980 Feb 23, 2026
8bdda2b
Merge branch 'main' into yoo/spec-transformer
jyoo980 Feb 23, 2026
1b987fd
Adding updates to inferring non-null preconditions
jyoo980 Feb 23, 2026
4858d9b
Fixing build errors
jyoo980 Feb 23, 2026
aca35e9
Applying fix
jyoo980 Feb 23, 2026
e2d15c1
Documenting non-null precondition inference transformation
jyoo980 Feb 23, 2026
dac83ed
Starting implementation of minimize conditional assigns
jyoo980 Feb 23, 2026
503a4fc
Adding test for multiple conditions
jyoo980 Feb 23, 2026
7a3b930
Merge branch 'main' into yoo/spec-transformer
jyoo980 Feb 24, 2026
4b5d416
Merge branch 'main' into yoo/spec-transformer
jyoo980 Mar 2, 2026
7adf3a2
Merge branch 'main' into yoo/spec-transformer
jyoo980 Mar 5, 2026
f638020
Merge branch 'main' into yoo/spec-transformer
jyoo980 Mar 12, 2026
96e71f6
Merge branch 'main' into yoo/spec-transformer
jyoo980 Mar 17, 2026
e538b5f
Merge branch 'main' into yoo/spec-transformer
jyoo980 Mar 22, 2026
ed574a7
Merge branch 'main' into yoo/spec-transformer
jyoo980 Mar 28, 2026
504a511
Merge branch 'main' into yoo/spec-transformer
jyoo980 Mar 28, 2026
1cb01a3
Merge branch 'main' into yoo/spec-transformer
jyoo980 Mar 30, 2026
8054263
Merge branch 'main' into yoo/spec-transformer
jyoo980 Mar 30, 2026
bb07432
Merge branch 'main' into yoo/spec-transformer
jyoo980 Mar 31, 2026
c28f2b5
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 1, 2026
f8eb6bf
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 2, 2026
ee4556b
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 3, 2026
fc58525
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 3, 2026
d01c8cb
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 7, 2026
e642b26
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 8, 2026
61372c0
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 10, 2026
bef92c4
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 12, 2026
095d7d0
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 13, 2026
4297167
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 14, 2026
c0ada6b
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 15, 2026
45c7dd3
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 15, 2026
3a26b9b
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 16, 2026
3bea01c
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 16, 2026
0e5a92b
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 19, 2026
9eb6c21
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 20, 2026
08922b5
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 20, 2026
d0128a5
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 20, 2026
bc0e97d
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 20, 2026
966830f
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 21, 2026
05d7a49
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 22, 2026
7209a09
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 23, 2026
9cd1bd0
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 23, 2026
174f984
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 23, 2026
72a9ac7
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 23, 2026
ac79fd5
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 24, 2026
2377b21
Merge branch 'main' into yoo/spec-transformer
jyoo980 Apr 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions specifications/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .llm_specification_generator import LlmSpecificationGenerator
from .specification_extractor import extract_spec_from_response
from .simplify_spec import simplify
from .specification_extractor import extract_spec_from_response
from .variants.specification_variant_factory import SpecificationVariantFactory

__all__ = ["LlmSpecificationGenerator", "extract_spec_from_response", "simplify"]
__all__ = ["LlmSpecificationGenerator", "extract_spec_from_response", "simplify", "SpecificationVariantFactory"]
3 changes: 3 additions & 0 deletions specifications/variants/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .specification_variant_factory import SpecificationVariantFactory

__all__ = ["SpecificationVariantFactory"]
44 changes: 44 additions & 0 deletions specifications/variants/specification_variant_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Class for symbolically generating variants of a specification."""

from util import FunctionSpecification

from .transformations import (
InferNonNullPreconditionsFromEnsures,
MovePreconditionsToAssignsAndEnsures,
RemovePreconditions,
SpecificationTransformation,
)


class SpecificationVariantFactory:
"""Class for symbolically generating variants of a specification.

Attributes:
(_transformations): tuple[SpecificationTransformation, ...]: The transformations to apply
to a specification to generate variants.

"""

_transformations: tuple[SpecificationTransformation, ...]

def __init__(self) -> None:
"""Create a new SpecificationVariantFactory."""
self._transformations = (
MovePreconditionsToAssignsAndEnsures(),
RemovePreconditions(),
InferNonNullPreconditionsFromEnsures(),
)

def get_variants(self, spec: FunctionSpecification) -> tuple[FunctionSpecification, ...]:
"""Return the variants of the given specification.

Args:
spec (FunctionSpecification): The specifications for which to generate variants.

Returns:
tuple[FunctionSpecification, ...]: The variants of the given specification.
"""
variants = set()
for transformation in self._transformations:
variants.update(transformation.apply(spec))
return tuple(variants)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

tuple(variants) has non-deterministic ordering across runs.

variants is a set; due to Python's PYTHONHASHSEED randomisation, tuple(variants) produces a different ordering on each interpreter invocation. If callers depend on a stable iteration order (e.g., prioritising variants), this will behave non-reproducibly.

♻️ Proposed fix — sort before converting to tuple
-        return tuple(variants)
+        return tuple(
+            sorted(variants, key=lambda s: (s.preconditions, s.postconditions))
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specifications/variants/specification_variant_factory.py` at line 35, The
current return uses tuple(variants) where variants is a set, which yields
non-deterministic ordering; change it to produce a deterministic ordering by
sorting before converting to a tuple (e.g., use sorted(variants, key=str) or
another stable key appropriate for the variant objects) so the return value from
the line returning tuple(variants) is reproducible across runs.

Comment on lines +32 to +44
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

get_variants will propagate ValueError from RemovePreconditions for preconditions-only specs.

When spec has preconditions but no postconditions, RemovePreconditions.apply crashes with ValueError (as noted in that file's review). Since get_variants does not catch exceptions from transformation.apply, the error will bubble up here. This will be fixed once the early-return guard is added to RemovePreconditions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specifications/variants/specification_variant_factory.py` around lines 27 -
39, get_variants currently iterates self._transformations and calls
transformation.apply(spec) which can propagate a ValueError from
RemovePreconditions.apply for specs that have only preconditions; wrap the call
to transformation.apply in a try/except ValueError and skip that transformation
on error (optionally log/debug), and/or add a fast-path: if
isinstance(transformation, RemovePreconditions) and spec.postconditions is
empty, skip calling transformation.apply entirely; update references in code to
_transformations, get_variants, transformation.apply and RemovePreconditions so
the method no longer bubbles that ValueError.

7 changes: 7 additions & 0 deletions specifications/variants/transformations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .specification_transformation import SpecificationTransformation
from .move_preconditions_to_assigns_and_ensures import MovePreconditionsToAssignsAndEnsures
from .remove_preconditions import RemovePreconditions
from .infer_non_null_preconditions_from_ensures import InferNonNullPreconditionsFromEnsures
from .minimize_conditional_assigns import MinimizeConditionalAssigns

__all__ = ["SpecificationTransformation", "MovePreconditionsToAssignsAndEnsures", "RemovePreconditions", "InferNonNullPreconditionsFromEnsures", "MinimizeConditionalAssigns"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""Transformation for inferring non-null preconditions from ensures clauses."""

from lark.tree import Meta

from translation.ast.cbmc_ast import (
CbmcAst,
EnsuresClause,
IndexOp,
Name,
NeqOp,
PtrMemberOp,
RequiresClause,
)
from util import FunctionSpecification

from .specification_transformation import SpecificationTransformation


class InferNonNullPreconditionsFromEnsures(SpecificationTransformation):
"""Transformation for inferring non-null preconditions from ensures clauses."""

def apply(self, specification: FunctionSpecification) -> list[FunctionSpecification]:
"""Return the result of applying this transformation to the given specification.

Infer non-null preconditions from an ensures clause.

For example, given a spec with the following ensures clause:

__CPROVER_ensures(a->b->c != NULL)

The following preconditions are generated:

__CPROVER_requires(a != NULL)
__CPROVER_requires(a->b != NULL)

Args:
specification (FunctionSpecification): The specification to transform.

Returns:
list[FunctionSpecification]: The result of applying this transformation to the given
specification.
"""
precondition_asts, postcondition_asts = self._parse_specification(specification)
non_null_check_exprs: list[NeqOp] = [
non_null_check_expr
for ast in postcondition_asts
if (non_null_check_expr := self._get_non_null_check_expr(ast))
]
precondition_exprs = []
for neqop in non_null_check_exprs:
non_null_check_subexprs = self._get_non_null_check_subexpressions(neqop.left)
precondition_exprs = [
NeqOp(subexpr, neqop.right) for subexpr in non_null_check_subexprs
]
Comment on lines +49 to +54
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

precondition_exprs is overwritten on every loop iteration — preconditions from all but the last NeqOp are silently dropped.

When non_null_check_exprs contains more than one entry (e.g., a spec with two non-null ensures clauses), the assignment precondition_exprs = [...] on Line 40 discards any results accumulated by prior iterations, so only the final neqop's subexpressions are ever wrapped into RequiresClause nodes.

🐛 Proposed fix: accumulate with `+=`
 precondition_exprs = []
 for neqop in non_null_check_exprs:
     non_null_check_subexprs = self._get_non_null_check_subexpressions(neqop.left)
-    precondition_exprs = [
+    precondition_exprs += [
         NeqOp(subexpr, neqop.right) for subexpr in non_null_check_subexprs
     ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@specifications/variants/transformations/infer_non_null_preconditions_from_ensures.py`
around lines 37 - 42, The loop currently overwrites precondition_exprs for each
NeqOp so only the last non-null check survives; in the loop inside
infer_non_null_preconditions_from_ensures.py change the assignment that creates
new NeqOp(...) list to accumulate results instead (e.g., extend
precondition_exprs with the list returned from mapping NeqOp over
self._get_non_null_check_subexpressions(neqop.left) and neqop.right) so
precondition_exprs collects entries from all non_null_check_exprs, ensuring
later construction of RequiresClause uses the full set.

new_precondition_clauses = [
# The `Meta` here shouldn't matter, since it isn't used when inserting specs.
RequiresClause(meta=Meta(), expr=expr)
for expr in precondition_exprs
]
updated_precondition_clauses = precondition_asts + new_precondition_clauses
return [
FunctionSpecification(
preconditions=[clause.to_string() for clause in updated_precondition_clauses],
postconditions=specification.postconditions,
)
]

def _get_non_null_check_expr(self, ast: CbmcAst) -> NeqOp | None:
"""Return a non-null check expression in an ensures clause, if found.

Args:
ast (CbmcAst): The AST in which to search for a non-null check expression.

Returns:
NeqOp | None: The non-null check expression, if found.
"""
if not isinstance(ast, EnsuresClause):
return None
match ast.expr:
case NeqOp(left=PtrMemberOp(_, _), right=Name("NULL")):
return ast.expr
case NeqOp(left=IndexOp(value=PtrMemberOp(_, _)), right=Name("NULL")):
return ast.expr
case _:
return None

def _get_non_null_check_subexpressions(self, expr: CbmcAst) -> list[CbmcAst]:
"""Return the subexpressions from the left-hand side of a non-null check.

Note: This function assumes the non-null check is of the form `LHS != NULL`.

Args:
expr (CbmcAst): The left-hand side of a non-null check.

Returns:
list[CbmcAst]: The subexpressions from the left-hand side of a non-null check.
"""
result = []
match expr:
case PtrMemberOp(_, _):
subexprs = expr.get_dereference_subexpressions()
# Exclude self to avoid duplicating the existing ensures clause.
result = [s for s in subexprs if s is not expr]
case IndexOp(value=PtrMemberOp(_, _), index=_):
subexprs = expr.value.get_dereference_subexpressions()
result = [s for s in subexprs if s is not expr.value]
Comment on lines +104 to +106
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

IndexOp branch incorrectly excludes expr.value (a->b) from the inferred preconditions.

For an ensures clause like a->b[0] != NULL, _get_non_null_check_subexpressions receives IndexOp(value=PtrMemberOp(a, b), index=0) as expr. The call to expr.value.get_dereference_subexpressions() on a->b correctly returns [a, a->b]. However, the filter s is not expr.value removes a->b from the result, leaving only [a], so only a != NULL is generated as a precondition.

This silently drops a->b != NULL, which is required for a->b[0] to be a valid dereference. The comment "Exclude self to avoid duplicating the existing ensures clause" applies to the PtrMemberOp branch (where expr is the ensures LHS), not here — expr.value (a->b) is distinct from the ensures LHS (a->b[0]), so filtering it is wrong.

🐛 Proposed fix
             case IndexOp(value=PtrMemberOp(_, _), index=_):
                 subexprs = expr.value.get_dereference_subexpressions()
-                result = [s for s in subexprs if s is not expr.value]
+                result = subexprs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@specifications/variants/transformations/infer_non_null_preconditions_from_ensures.py`
around lines 104 - 106, In the IndexOp(value=PtrMemberOp(_, _), index=_) branch
of _get_non_null_check_subexpressions, do not filter out expr.value from the
dereference subexpressions; the current line calling
expr.value.get_dereference_subexpressions() then excluding "s is not expr.value"
wrongly drops the PtrMemberOp itself (e.g. a->b) and loses the required
precondition a->b != NULL. Remove that exclusion (or only apply it in the
PtrMemberOp branch where expr itself equals the ensures LHS) so that the list
returned from expr.value.get_dereference_subexpressions() includes expr.value
when handling IndexOp, ensuring both a != NULL and a->b != NULL are inferred.

return result
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Transformation where the condition in an assigns clause is minimized."""

from translation.ast.cbmc_ast import Assigns, CbmcAst, LogicalBinOp
from util import FunctionSpecification

from .specification_transformation import SpecificationTransformation


class MinimizeConditionalAssigns(SpecificationTransformation):
"""Transformation where the condition in an assigns clause is minimized."""

def apply(self, specification: FunctionSpecification) -> list[FunctionSpecification]:
"""Return the result of applying this transformation to the given specification.

Given a specification that has a postcondition such as:

__CPROVER_assigns(a || b || c : result)

Return specifications that are identical to the original, but with disjuncts removed
one-by-one, i.e.,

__CPROVER_assigns(a || b : result)
__CPROVER_assigns(a : result)
__CPROVER_assigns(result)

Args:
specification (FunctionSpecification): The specification to transform.

Returns:
list[FunctionSpecification]: The result of applying this transformation to the given
specification.
"""
_, postconditions = self._parse_specification(specification)
assigns_clauses = [clause for clause in postconditions if isinstance(clause, Assigns)]

if not assigns_clauses:
return [specification]

assigns_clause = assigns_clauses[0]

# If there is no condition, nothing to minimize.
if assigns_clause.condition is None:
return [specification]

preconditions = list(specification.preconditions)
transformed_specs: list[FunctionSpecification] = []

# Build the non-assigns postconditions (unchanged across variants).
other_postconditions = [
clause.to_string() for clause in postconditions if not isinstance(clause, Assigns)
]
Comment on lines +48 to +51
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve other Assigns clauses instead of dropping them.

other_postconditions currently removes all Assigns, so any additional assigns clauses in the original spec are silently lost in every variant. If multiple assigns are valid, this changes semantics. Keep all postconditions except the one being minimized (or assert there is only one assigns clause).

🛠️ Suggested fix (keep all clauses except the target assigns)
-        other_postconditions = [
-            clause.to_string() for clause in postconditions if not isinstance(clause, Assigns)
-        ]
+        other_postconditions = [
+            clause.to_string()
+            for clause in postconditions
+            if clause is not assigns_clause
+        ]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Build the non-assigns postconditions (unchanged across variants).
other_postconditions = [
clause.to_string() for clause in postconditions if not isinstance(clause, Assigns)
]
# Build the non-assigns postconditions (unchanged across variants).
other_postconditions = [
clause.to_string()
for clause in postconditions
if clause is not assigns_clause
]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@specifications/variants/transformations/minimize_conditional_assigns.py`
around lines 48 - 51, The current construction of other_postconditions drops all
Assigns clauses (using postconditions and Assigns); change it to preserve every
postcondition except the particular assigns clause being minimized (instead of
filtering out all Assigns). Locate the assigns clause selected for minimization
(e.g., the variable that represents the target assigns clause in this variant)
and build other_postconditions by keeping clause for clause in postconditions if
clause is not that target_assigns (or, if no explicit target variable exists,
assert there is exactly one Assigns in postconditions and keep all non-target
clauses). Ensure you reference and compare the actual Assigns instance rather
than the Assigns type when filtering.


# If the condition is a LogicalBinOp, generate prefix variants.
if isinstance(assigns_clause.condition, LogicalBinOp):
prefixes = assigns_clause.condition.get_operand_prefixes()
for prefix_condition in reversed(prefixes):
new_assigns = Assigns(condition=prefix_condition, targets=assigns_clause.targets)
new_postconditions = [*other_postconditions, new_assigns.to_string()]
transformed_specs.append(
FunctionSpecification(
preconditions=preconditions,
postconditions=new_postconditions,
)
)

# Always add a variant with no condition at all (just the assigns target).
no_condition_assigns = Assigns(condition=None, targets=assigns_clause.targets)
no_condition_postconditions = [*other_postconditions, no_condition_assigns.to_string()]
transformed_specs.append(
FunctionSpecification(
preconditions=preconditions,
postconditions=no_condition_postconditions,
)
)

return transformed_specs

def _get_operand_prefixes(self, logical_binop: LogicalBinOp) -> list[CbmcAst]:
"""Return the strict prefixes of the given logical binary operation.

E.g., Given `a || b || c || d`, return `a`, `a || b`, `a || b || c`.

Args:
logical_binop (LogicalBinOp): The logical binary operation for which to return prefixes.

Returns:
list[CbmcAst]: The strict prefixes of the logical binary operation.
"""
operands = logical_binop.get_operand_atoms()
if len(operands) <= 1:
return []
variants: list[CbmcAst] = []
for i in range(1, len(operands)):
prefix = operands[:i]
variants.append(logical_binop.apply(prefix))
return variants
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Transformation where expressions in preconditions are moved to assigns and ensures clauses."""

from translation.ast.cbmc_ast import (
AndOp,
Assigns,
CbmcAst,
EnsuresClause,
OrOp,
RequiresClause,
)
from util import FunctionSpecification

from .specification_transformation import SpecificationTransformation


class MovePreconditionsToAssignsAndEnsures(SpecificationTransformation):
"""Transformation where precondition expressions are moved to assigns and ensures clauses.

There are cases where a specification might contain preconditions that are *too* strong,
effectively weakening the entire specification. This transformation moves precondition
expressions into conditions for assigns clauses and disjunctions for ensures clauses.
"""

def apply(self, specification: FunctionSpecification) -> list[FunctionSpecification]:
"""Return the result of applying this transformation to the given specification.

Move the expressions in preconditions clauses into:
* Conditions for any assigns clauses.
* Disjunctions with any ensures clauses.

Args:
specification (FunctionSpecification): The specification to transform.

Returns:
list[FunctionSpecification]: The result of applying this transformation to the given
specification.
"""
precondition_asts, postcondition_asts = self._parse_specification(specification)

# If there are no postconditions, return unchanged.
if not postcondition_asts:
return [specification]

# Extract the inner expressions from each RequiresClause.
precondition_exprs: list[CbmcAst] = [
clause.expr for clause in precondition_asts if isinstance(clause, RequiresClause)
]

# If there are no precondition expressions, return unchanged.
if not precondition_exprs:
return [specification]

new_postconditions: list[str] = []
for post_ast in postcondition_asts:
if isinstance(post_ast, EnsuresClause):
# Build disjunction: !pre1 || !pre2 || ... || ensures_expr
result: CbmcAst = precondition_exprs[0].negate()
for pre_expr in precondition_exprs[1:]:
result = OrOp(left=result, right=pre_expr.negate())
result = OrOp(left=result, right=post_ast.expr)
new_postconditions.append(
EnsuresClause(meta=post_ast.meta, expr=result).to_string()
)
elif isinstance(post_ast, Assigns):
# Add conjunction of all precondition expressions as a condition.
condition: CbmcAst = precondition_exprs[0]
for pre_expr in precondition_exprs[1:]:
condition = AndOp(left=condition, right=pre_expr)
# If there's already a condition, AND it with the preconditions.
if post_ast.condition:
condition = AndOp(left=post_ast.condition, right=condition)
new_assigns = Assigns(condition=condition, targets=post_ast.targets)
new_postconditions.append(new_assigns.to_string())
else:
new_postconditions.append(post_ast.to_string())

return [
FunctionSpecification(
preconditions=[],
postconditions=new_postconditions,
)
]
Loading