Skip to content

Keep an enum constant a contract still names - #216

Draft
nikswamy wants to merge 2 commits into
mainfrom
nswamy/pal-pr04a-prune-enum-constants
Draft

Keep an enum constant a contract still names#216
nikswamy wants to merge 2 commits into
mainfrom
nswamy/pal-pr04a-prune-enum-constants

Conversation

@nikswamy

Copy link
Copy Markdown
Contributor

Part of the upstreaming of nswamy/pal-c-project-integration (PR04a of 35 PRs) — see PR_PLAN.md on that branch for the whole plan and the dependency graph.

Base: nswamy/pal-pr03-enum-constants-in-contracts — a stacked PR. It depends on nswamy/pal-pr03-enum-constants-in-contracts, nswamy/pal-pr19-switch-enum-scrutinee, so only the commits listed below are its own; it will be retargeted at main once its parents land.

It has more than one parent, so the diff against nswamy/pal-pr03-enum-constants-in-contracts also carries the commits from nswamy/pal-pr19-switch-enum-scrutinee until those land.

The pruner drops declarations that are not from the main file, and it only kept what it
could see referenced. A bare variable reference in a contract recorded nothing, so an
enumerator a contract still names was pruned away and the generated module referred to a
constant that no longer existed. The scan now records a global-variable dependency for it,
which over-approximates in the safe direction.

Commits

  • Keep an enum constant a contract still names

Testing

Verified: make rust lib, test/check-template.sh, cargo fmt --check, clang-format --dry-run --Werror, and F* verification of test/enum_const_in_contract, test/enum_constant_in_contract.

@nikswamy
nikswamy force-pushed the nswamy/pal-pr04a-prune-enum-constants branch from a701397 to 6ff7a6a Compare August 20, 2026 06:17
Base automatically changed from nswamy/pal-pr03-enum-constants-in-contracts to main August 24, 2026 16:28
nikswamy and others added 2 commits August 24, 2026 09:28
Clang applies the integral promotions to a switch condition, so the
translated condition carries the promoted integer type. An enumeration is
modeled by its underlying integer type, which makes that promotion the
identity, so the rvalue translation elides it. The scrutinee binding was
still annotated with the promoted type, leaving the declared type and the
bound value syntactically distinct. The Pulse dereference tactic compares
types syntactically and rejected the binding.

Annotate the binding with the enumeration's own type when the unpromoted
condition is an enumeration. Other promotions remain genuine casts that the
rvalue translation preserves, so their promoted type is still correct.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 981f7d3c-6a91-47ad-84d7-7aadf747123d
(cherry picked from commit 588982d)
A contract is often the only place an enumerator appears by name: a switch
over the same enum reaches it through case labels, which the frontend folds
to integers before the pruner runs. The pruner recorded no dependency for a
bare variable reference at all, so such an enumerator was dropped and the
contract then failed to resolve it.

Record a global-variable dependency for every name reference. Locals share
the namespace, so this keeps a few declarations that are not needed -- the
safe direction for a pruner, which must never drop one that is.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 981f7d3c-6a91-47ad-84d7-7aadf747123d
(cherry picked from commit 3b88a43)
@gebner
gebner force-pushed the nswamy/pal-pr04a-prune-enum-constants branch from 6ff7a6a to d0b403b Compare August 24, 2026 16:28
@gebner

gebner commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

!diff

@github-actions

Copy link
Copy Markdown

Generated F* output diff

Effect of this pull request on the F* code pal generates for the test suite (1f697c0d0b403b).

Summary
 /dev/null => head/enum_const_in_contract/Func_color_default.fst   | 14 ++++++++++++++
 /dev/null => head/enum_const_in_contract/Func_color_default.fsti  | 11 +++++++++++
 /dev/null => head/enum_const_in_contract/Func_color_is_known.fst  | 41 +++++++++++++++++++++++++++++++++++++++++
 /dev/null => head/enum_const_in_contract/Func_color_is_known.fsti | 16 ++++++++++++++++
 /dev/null => head/enum_const_in_contract/Global_Color_Blue.fst    |  6 ++++++
 /dev/null => head/enum_const_in_contract/Global_Color_Green.fst   |  6 ++++++
 /dev/null => head/enum_const_in_contract/Global_Color_Red.fst     |  6 ++++++
 /dev/null => head/enum_const_in_contract/TranslationErrors.fst    |  1 +
 /dev/null => head/enum_const_in_contract/Typedef_COLOR.fst        | 11 +++++++++++
 /dev/null => head/enum_const_in_contract/diagnostics.json         |  1 +
 10 files changed, 113 insertions(+)

Full diff: full diff artifact

Diff
diff --git head/enum_const_in_contract/Func_color_default.fst head/enum_const_in_contract/Func_color_default.fst
new file mode 100644
index 0000000..14c2017
--- /dev/null
+++ head/enum_const_in_contract/Func_color_default.fst
@@ -0,0 +1,14 @@
+module Func_color_default
+open Pulse
+open Pulse.Lib.C
+#lang-pulse
+
+divergent fn func_color_default ()
+  returns return_1 : Typedef_COLOR.ty_color
+  ensures ((Typedef_COLOR.ty_color__pred return_1 1.0R))
+  ensures
+    (with_pure
+      (return_1 = (id #UInt32.t (Int.Cast.int32_to_uint32 Global_Color_Green.var_color_green))))
+{
+  return (id #UInt32.t (Int.Cast.int32_to_uint32 1l));
+}
\ No newline at end of file
diff --git head/enum_const_in_contract/Func_color_default.fsti head/enum_const_in_contract/Func_color_default.fsti
new file mode 100644
index 0000000..e5bd150
--- /dev/null
+++ head/enum_const_in_contract/Func_color_default.fsti
@@ -0,0 +1,11 @@
+module Func_color_default
+open Pulse
+open Pulse.Lib.C
+#lang-pulse
+
+divergent fn func_color_default ()
+returns return_1 : Typedef_COLOR.ty_color
+ensures ((Typedef_COLOR.ty_color__pred return_1 1.0R))
+ensures
+  (with_pure
+    (return_1 = (id #UInt32.t (Int.Cast.int32_to_uint32 Global_Color_Green.var_color_green))))
\ No newline at end of file
diff --git head/enum_const_in_contract/Func_color_is_known.fst head/enum_const_in_contract/Func_color_is_known.fst
new file mode 100644
index 0000000..15a6cac
--- /dev/null
+++ head/enum_const_in_contract/Func_color_is_known.fst
@@ -0,0 +1,41 @@
+module Func_color_is_known
+open Pulse
+open Pulse.Lib.C
+#lang-pulse
+
+divergent fn func_color_is_known (var_c: Typedef_COLOR.ty_color)
+  requires ((Typedef_COLOR.ty_color__pred var_c 1.0R))
+  returns return_1 : bool
+  ensures ((Typedef_COLOR.ty_color__pred var_c 1.0R))
+  ensures
+    (with_pure
+      (return_1 =
+        (((var_c = (id #UInt32.t (Int.Cast.int32_to_uint32 Global_Color_Red.var_color_red))) ||
+            (var_c = (id #UInt32.t (Int.Cast.int32_to_uint32 Global_Color_Green.var_color_green))))
+          ||
+          (var_c = (id #UInt32.t (Int.Cast.int32_to_uint32 Global_Color_Blue.var_color_blue))))))
+{
+  let mut var_c = var_c;
+  let mut var_known : bool;
+  var_known := (int32_to_bool 0l);
+  let var___switch_scrut_0 : Typedef_COLOR.ty_color = (!var_c);
+  let mut var___switch_hit_0 : bool;
+  var___switch_hit_0 := false;
+  let mut var___switch_brk_0 : bool;
+  var___switch_brk_0 := false;
+  if (((not (!var___switch_brk_0)) &&
+        ((!var___switch_hit_0) ||
+          (((false || (var___switch_scrut_0 = (id #UInt32.t (Int.Cast.int32_to_uint32 0l)))) ||
+              (var___switch_scrut_0 = (id #UInt32.t (Int.Cast.int32_to_uint32 1l))))
+            ||
+            (var___switch_scrut_0 = (id #UInt32.t (Int.Cast.int32_to_uint32 2l))))))) {
+    var___switch_hit_0 := true;
+    var_known := (int32_to_bool 1l);
+    var___switch_brk_0 := true;
+  } else {};
+  if ((not (!var___switch_brk_0))) {
+    var___switch_hit_0 := true;
+    var___switch_brk_0 := true;
+  } else {};
+  return (!var_known);
+}
\ No newline at end of file
diff --git head/enum_const_in_contract/Func_color_is_known.fsti head/enum_const_in_contract/Func_color_is_known.fsti
new file mode 100644
index 0000000..81f3293
--- /dev/null
+++ head/enum_const_in_contract/Func_color_is_known.fsti
@@ -0,0 +1,16 @@
+module Func_color_is_known
+open Pulse
+open Pulse.Lib.C
+#lang-pulse
+
+divergent fn func_color_is_known (var_c: Typedef_COLOR.ty_color)
+requires ((Typedef_COLOR.ty_color__pred var_c 1.0R))
+returns return_1 : bool
+ensures ((Typedef_COLOR.ty_color__pred var_c 1.0R))
+ensures
+  (with_pure
+    (return_1 =
+      (((var_c = (id #UInt32.t (Int.Cast.int32_to_uint32 Global_Color_Red.var_color_red))) ||
+          (var_c = (id #UInt32.t (Int.Cast.int32_to_uint32 Global_Color_Green.var_color_green))))
+        ||
+        (var_c = (id #UInt32.t (Int.Cast.int32_to_uint32 Global_Color_Blue.var_color_blue))))))
\ No newline at end of file
diff --git head/enum_const_in_contract/Global_Color_Blue.fst head/enum_const_in_contract/Global_Color_Blue.fst
new file mode 100644
index 0000000..29337f2
--- /dev/null
+++ head/enum_const_in_contract/Global_Color_Blue.fst
@@ -0,0 +1,6 @@
+module Global_Color_Blue
+open Pulse
+open Pulse.Lib.C
+#lang-pulse
+
+let var_color_blue : Int32.t = 2l
\ No newline at end of file
diff --git head/enum_const_in_contract/Global_Color_Green.fst head/enum_const_in_contract/Global_Color_Green.fst
new file mode 100644
index 0000000..8c8ecf7
--- /dev/null
+++ head/enum_const_in_contract/Global_Color_Green.fst
@@ -0,0 +1,6 @@
+module Global_Color_Green
+open Pulse
+open Pulse.Lib.C
+#lang-pulse
+
+let var_color_green : Int32.t = 1l
\ No newline at end of file
diff --git head/enum_const_in_contract/Global_Color_Red.fst head/enum_const_in_contract/Global_Color_Red.fst
new file mode 100644
index 0000000..eccb0c1
--- /dev/null
+++ head/enum_const_in_contract/Global_Color_Red.fst
@@ -0,0 +1,6 @@
+module Global_Color_Red
+open Pulse
+open Pulse.Lib.C
+#lang-pulse
+
+let var_color_red : Int32.t = 0l
\ No newline at end of file
diff --git head/enum_const_in_contract/TranslationErrors.fst head/enum_const_in_contract/TranslationErrors.fst
new file mode 100644
index 0000000..bf62561
--- /dev/null
+++ head/enum_const_in_contract/TranslationErrors.fst
@@ -0,0 +1 @@
+module TranslationErrors
diff --git head/enum_const_in_contract/Typedef_COLOR.fst head/enum_const_in_contract/Typedef_COLOR.fst
new file mode 100644
index 0000000..b25f661
--- /dev/null
+++ head/enum_const_in_contract/Typedef_COLOR.fst
@@ -0,0 +1,11 @@
+module Typedef_COLOR
+open Pulse
+open Pulse.Lib.C
+#lang-pulse
+
+unfold
+let ty_color : Type = UInt32.t
+[@@pulse_eager_unfold] let predicate ty_color__pred ([@@@mkey] this: ty_color) (p: perm) = emp
+[@@pulse_eager_unfold] let predicate ty_color__uninit_pred ([@@@mkey] this: ty_color) = emp
+instance has_zero_default_ty_color : (has_zero_default ty_color) =
+  { zero_default = (UInt32.uint_to_t 0) }
\ No newline at end of file
diff --git head/enum_const_in_contract/diagnostics.json head/enum_const_in_contract/diagnostics.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ head/enum_const_in_contract/diagnostics.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants