Skip to content

Custard: fold projections of a let-bound constructor - #4553

Merged
gebner merged 1 commit into
masterfrom
gebner_custard_letpatfold
Sep 19, 2026
Merged

gebner merged 1 commit into
masterfrom
gebner_custard_letpatfold

Conversation

@gebner

@gebner gebner commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Fixes #4548.

let (a, (b, _)) = p in an inline_for_extraction callee left the caller building a tuple, naming it and reading it back out:

uint32_t LetPatFold_entry(uint32_t x) {
  FStar_Pervasives_Native_tuple2__uint32_tuple2_uint32_unit _letpattern =
      (FStar_Pervasives_Native_tuple2__uint32_tuple2_uint32_unit){
          ._1 = x,
          ._2 = (FStar_Pervasives_Native_tuple2__uint32_unit){ ._1 = (x + 1) } };
  return (_letpattern._1 + _letpattern._2._1);
}

Now it is return (x + (x + 1));.

The rewrites were already there -- iota on a match over a constructor, unbuild on a projection out of one -- but F* names the scrutinee first (let _letpattern = Mktuple2 ... in match _letpattern with ...), so neither ever sees a constructor.

  • reduce's ELet case substitutes a let-bound constructor when every occurrence is destructed on the spot (the scrutinee of a match, or the target of a projection) and every field is reeval.
  • unbuild does the same for the projection-only shape that depat leaves behind, using psub plus a guard that the body does not rebind the name.
  • reeval is the condition the immediate case never needed: a field that is read moves, possibly more than once and possibly into a loop, so it is restricted to the bounded, allocation-free class -- variables, constants, projections, casts, coercions, tag reads, operators, and constructors over them. A call or an allocation keeps its binding, which is the issue's own caveat about discarded components. Constructors have to be in it or the rule does not fire on the nested tuple it was written for.

No declared type changes: the one-field struct a unit tail leaves is still declared, it is just no longer built.

Documented in section 129 of doc/ref/custard.md. New regression test tests/custard/pulse/LetPatFold.fst. The full gate is green and no goldens moved.

@gebner
gebner force-pushed the gebner_custard_letpatfold branch from 1ece7e1 to c01be8c Compare September 19, 2026 00:00
@gebner
gebner enabled auto-merge September 19, 2026 00:37
@gebner
gebner added this pull request to the merge queue Sep 19, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Sep 19, 2026
Fixes #4548.  `let (a, (b, _)) = p` in an inlined callee left the caller
building a tuple, naming it and reading it back out.  The rewrites were
already there -- `iota` on a `match` over a constructor, `unbuild` on a
projection out of one -- but F* puts a binding in front of the scrutinee,
so neither ever sees a constructor.

`reduce` now substitutes a let-bound constructor when every occurrence is
destructed on the spot and every field is `reeval`; `unbuild` does the
same for the projection-only shape that `depat` leaves behind.  `reeval`
is bounded, allocation-free and safe to repeat: variables, constants,
projections, casts, operators, and constructors over them.  A call or an
allocation keeps its binding.

Documented in section 129.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gebner
gebner force-pushed the gebner_custard_letpatfold branch from c01be8c to b9cadbb Compare September 19, 2026 02:01
@gebner
gebner enabled auto-merge September 19, 2026 02:26
@gebner
gebner added this pull request to the merge queue Sep 19, 2026
Merged via the queue into master with commit 5603331 Sep 19, 2026
5 checks passed
@gebner
gebner deleted the gebner_custard_letpatfold branch September 19, 2026 04:01
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.

Custard: projection of a constructor bound in the same block is not folded

1 participant