Skip to content

where { const_expr } predicates in where clauses#1446

Draft
micahscopes wants to merge 14 commits into
argotorg:masterfrom
micahscopes:const-predicates
Draft

where { const_expr } predicates in where clauses#1446
micahscopes wants to merge 14 commits into
argotorg:masterfrom
micahscopes:const-predicates

Conversation

@micahscopes

@micahscopes micahscopes commented May 16, 2026

Copy link
Copy Markdown
Collaborator

This PR makes ConstraintListId the canonical internal representation for where-clause assumptions and type-checking obligations, with const predicates treated as ordinary internal constraints.

Const predicates now participate in the same obligation flow as trait constraints:

  • declarations collect trait and const predicates into ConstraintListId
  • call sites instantiate callee constraints as ConstraintId obligations
  • generic const predicates can be proven from ParamEnv assumptions
  • concrete const predicates are discharged by CTFE bool evaluation
  • impl candidate const side conditions are returned as residual obligations
  • ADT/type well-formedness and trait method conformance account for const predicates

PredicateListId remains only as a compatibility projection for the current trait solver and resolver seams. Public Constraint kind syntax, Evidence<C>, typed reflection, generated overlays, and metaprogramming providers are intentionally out of scope for this PR.

Validation:

  • CARGO_TARGET_DIR=/tmp/fe-constraints-target cargo test -p fe-hir --test constraints
  • CARGO_TARGET_DIR=/tmp/fe-constraints-target cargo check -p fe-hir

Add support for where { const_expr } syntax in Fe where clauses.
Const predicates are boolean expressions evaluated via CTFE at
monomorphization time. If the expression evaluates to false,
compilation fails with a diagnostic.

Implementation spans 5 compiler layers:
- Parser: WhereConstPredicate AST node, LBrace-triggered parsing
- HIR: const_predicates field on WhereClauseId
- Lowering: Body lowering for const predicate expressions
- Diagnostics: ConstPredicateFailed error variant
- Call-site evaluation: CTFE eval in callable.rs at instantiation

This enables replacing #[arithmetic(unchecked)] with machine-checked
compile-time proofs. For example, a tuple encode_to_ptr can express
{ T0::HEAD_SIZE + T1::HEAD_SIZE >= T0::HEAD_SIZE } as a where clause
predicate instead of trusting the annotation.

Prototype status: compiles (parser + HIR), needs end-to-end testing
with actual Fe code using the new syntax.
When checking const predicates at a call site, also evaluate predicates
from the parent item's where clause (impl, impl-trait, trait). This is
needed for cases like const predicates on impl blocks:

  impl<T0, T1> Encode<Sol> for (T0, T1)
      where { T0::HEAD_SIZE + T1::HEAD_SIZE >= T0::HEAD_SIZE }

Previously only predicates on the function's own where clause were
evaluated. Now predicates propagate from the enclosing impl/trait.
Test that CTFE can evaluate a mandelbrot iteration loop inside a where
clause predicate. Uses fixed-point arithmetic (scale 1000) on the real
axis to check convergence:

- c=0.25 (in the set): predicate passes
- c=0.5 with 3 iterations (not yet escaped): predicate passes
- c=0.5 with 10 iterations (escapes at iter 5): predicate fails

Exercises loops, local mutation, and conditional returns in CTFE
predicate evaluation.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 969e1f5afb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread crates/parser/src/parser/param.rs Outdated
Comment thread crates/hir/src/analysis/ty/ty_check/callable.rs Outdated
The disambiguation heuristic incorrectly rejected const predicates in
trait methods and extern fns where nothing follows the predicate block.
Since is_where_const_predicate is only called when after_comma is true,
the block cannot be an item body — simplify to just validating the block
parses as an expression. Add regression test for trait method context.
Previously, if eval_body_owner_const returned Err (e.g. div-by-zero,
overflow), the predicate was silently treated as passing. Now emits
error[8-0083] so broken predicates properly block compilation.
Const predicates can now be written as bare expressions:
  where T: Sized, T::SIZE >= 50
in addition to the block form:
  where T: Sized, { T::SIZE >= 50 }

Disambiguation uses existing parser infrastructure:
- Type predicates: dry-run parse_type + check for ':'
- Bare expressions: delegates to parse_expr which already
  handles <>/generics disambiguation via PathSegmentScope
- Block predicates: '{' after comma is still a const predicate
  (after_comma invariant), not the item body

Trivia state is explicitly restored before the comma check to
prevent parse_expr sub-scopes from leaking newline_as_trivia.
@micahscopes

Copy link
Copy Markdown
Collaborator Author

before merging I'd like to take a look at rust's unstable feature(generic_const_exprs) and discussions around it

@sbillig

sbillig commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Does this work on trait impls?

eg impl<T> SomeTrait for T where size_of<T>() <= 32

When a generic function calls another with const predicates in its where
clause, the early detection pipeline tries to prove the predicates from
the caller's assumptions using structural expression comparison.

If the caller's where clause contains the same predicate (accounting for
generic param name mapping), the call is accepted at definition time.
Otherwise, silently defers to CTFE at monomorphization. No new errors,
no false negatives — purely earlier feedback.

Also fixes a latent bug where CTFE was attempted on generic args
containing type params (HAS_PARAM), which would always fail.
When the trait solver selects an impl candidate and the generic args
are fully resolved (concrete types), CTFE-evaluate the impl's where
clause const predicates. Reject the candidate if any predicate is false.

This means `impl<T> Foo for T where { T::SIZE <= 32 }` now only applies
to types whose SIZE is actually <= 32. Previously, the const predicate
was ignored during trait selection.

When types are still unresolved (inference vars or params), the check
is skipped and deferred to the call-site check in callable.rs.
@micahscopes

Copy link
Copy Markdown
Collaborator Author

@sbillig it should work now

When an impl's const predicate crashes during trait selection (e.g.
division by zero), the "doesn't implement Trait" error now includes a
note explaining that a predicate panicked and why.

Before:  `Zero` doesn't implement `SafeDiv`
After:   `Zero` doesn't implement `SafeDiv`
         = a const predicate on an impl of `SafeDiv` panicked: division by zero
@sbillig sbillig marked this pull request as draft May 23, 2026 03:38
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