Skip to content

Enable contracts for const functions #138374

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 15, 2025

Conversation

celinval
Copy link
Contributor

@celinval celinval commented Mar 11, 2025

Use const_eval_select!() macro to enable contract checking only at runtime. The existing contract logic relies on closures, which are not supported in constant functions.

This commit also removes one level of indirection for ensures clauses since we no longer build a closure around the ensures predicate.

Resolves #136925

Call-out: This is still a draft PR since CI is broken due to a new warning message for unreachable code when the bottom of the function is indeed unreachable. It's not clear to me why the warning wasn't triggered before.

r? @compiler-errors

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Mar 11, 2025
Copy link
Member

@compiler-errors compiler-errors left a comment

Choose a reason for hiding this comment

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

In what case is the unreachable error being generated? Can you turn it into a minimal example? I'm not totally sure what's going on with the code generation.

@celinval
Copy link
Contributor Author

I am still trying to understand why the new logic triggers the new warning, when the old one didn't.

In what case is the unreachable error being generated? Can you turn it into a minimal example? I'm not totally sure what's going on with the code generation.

So, if you look at the following example from one of the tests:

#![feature(contracts)]

pub struct Baz { baz: i32 }

#[core::contracts::requires(x.baz > 0)]
#[core::contracts::ensures(|ret| *ret > 100)]
pub fn nest(x: Baz) -> i32
{
    loop {
        return x.baz + 50;
    }
}

with the existing changes, you will get a warning:

warning: unreachable expression
  --> bar.rs:6:1
   |
6  | #[core::contracts::ensures(|ret| *ret > 100)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unreachable expression
...
10 |         return x.baz + 50;
   |         ----------------- any code following this expression is unreachable
   |

which in a way it makes sense, since we do add a call to check the post-condition after the loop statement. What I don't get is why it wasn't being triggered before, and whether I can mark the new code as allow(unreachable_code).

If I compile with Z unpretty=hir, this is what I get with this PR:

fn nest(x: Baz) -> i32 {
    #[lang = "contract_check_requires"](|| x.baz > 0);
    let __ensures_checker = #[lang = "contract_build_check_ensures"](|ret| *ret > 100);
    #[lang = "contract_check_ensures"]({
            loop { return #[lang = "contract_check_ensures"](x.baz + 50, __ensures_checker); }
    }, __ensures_checker)
}

before the changes, we had:

fn nest(x: Baz) -> i32 {
    #[lang = "contract_check_requires"](|| x.baz > 0);
    let __ensures_checker = #[lang = "contract_build_check_ensures"](|ret| *ret > 100);
    __ensures_checker({ loop { return __ensures_checker(x.baz + 50); } })
}

@celinval celinval force-pushed the issue-136925-const-contract branch from 2ff3baa to 4dd0bca Compare March 12, 2025 06:04
@rust-log-analyzer

This comment has been minimized.

@celinval
Copy link
Contributor Author

@compiler-errors any thoughts?

@compiler-errors
Copy link
Member

No, I didn't look at it yet

@fpoli
Copy link
Contributor

fpoli commented Mar 20, 2025

I am still trying to understand why the new logic triggers the new warning, when the old one didn't.

In the example, __ensures_checker is an unreachable expression because it's after the argument whose evaluation always returns. Normally, the compiler would highlight __ensures_checker. For example (playground):

warning: unreachable expression
  --> src/lib.rs:22:8
   |
21 |             loop { return contract_check_ensures(x.baz + 50, __ensures_checker); }
   |                    ------------------------------------------------------------ any code following this expression is unreachable
22 |     }, __ensures_checker)
   |        ^^^^^^^^^^^^^^^^^ unreachable expression
   |
   = note: `#[warn(unreachable_code)]` on by default

My understanding is that __ensures_checker is generated by a procedural macro, which attaches the span information of the ensures attribute to __ensures_checker. So, when the compiler tries to highlight __ensures_checker it actually highlights #[core::contracts::ensures(|ret| *ret > 100)]. If this explanation is correct, adding #[allow(unreachable_code)] just before the __ensures_checker in the generated code should suppress the warning:

fn nest(x: Baz) -> i32 {
    #[lang = "contract_check_requires"](|| x.baz > 0);
    let __ensures_checker = #[lang = "contract_build_check_ensures"](|ret| *ret > 100);
    #[lang = "contract_check_ensures"]({
            loop { return #[lang = "contract_check_ensures"](x.baz + 50, __ensures_checker); }
    }, #[allow(unreachable_code)] __ensures_checker)
}

@celinval
Copy link
Contributor Author

I am still trying to understand why the new logic triggers the new warning, when the old one didn't.

In the example, __ensures_checker is an unreachable expression because it's after the argument whose evaluation always returns. Normally, the compiler would highlight __ensures_checker. For example (playground):

warning: unreachable expression
  --> src/lib.rs:22:8
   |
21 |             loop { return contract_check_ensures(x.baz + 50, __ensures_checker); }
   |                    ------------------------------------------------------------ any code following this expression is unreachable
22 |     }, __ensures_checker)
   |        ^^^^^^^^^^^^^^^^^ unreachable expression
   |
   = note: `#[warn(unreachable_code)]` on by default

My understanding is that __ensures_checker is generated by a procedural macro, which attaches the span information of the ensures attribute to __ensures_checker. So, when the compiler tries to highlight __ensures_checker it actually highlights #[core::contracts::ensures(|ret| *ret > 100)]. If this explanation is correct, adding #[allow(unreachable_code)] just before the __ensures_checker in the generated code should suppress the warning:

fn nest(x: Baz) -> i32 {
    #[lang = "contract_check_requires"](|| x.baz > 0);
    let __ensures_checker = #[lang = "contract_build_check_ensures"](|ret| *ret > 100);
    #[lang = "contract_check_ensures"]({
            loop { return #[lang = "contract_check_ensures"](x.baz + 50, __ensures_checker); }
    }, #[allow(unreachable_code)] __ensures_checker)
}

The call is added as part of lowering the AST today. We can reconsider it though.

@bors
Copy link
Collaborator

bors commented Mar 27, 2025

☔ The latest upstream changes (presumably #138996) made this pull request unmergeable. Please resolve the merge conflicts.

Use `const_eval_select!()` macro to enable contract checking only at
runtime. The existing contract logic relies on closures,
which are not supported in constant functions.

This commit also removes one level of indirection for ensures clauses,
however, it currently has a spurious warning message when the bottom
of the function is unreachable.
@celinval celinval force-pushed the issue-136925-const-contract branch from 4dd0bca to 4f62bc2 Compare April 8, 2025 00:43
@celinval celinval marked this pull request as ready for review April 8, 2025 03:44
@rustbot
Copy link
Collaborator

rustbot commented Apr 8, 2025

Some changes occurred to the intrinsics. Make sure the CTFE / Miri interpreter
gets adapted for the changes, if necessary.

cc @rust-lang/miri, @RalfJung, @oli-obk, @lcnr

Some changes occurred to the CTFE machinery

cc @RalfJung, @oli-obk, @lcnr

Some changes occurred to constck

cc @fee1-dead

Invert the order that we pass the arguments to the
`contract_check_ensures` function to avoid the warning when the tail
of the function is unreachable.

Note that the call itself is also unreachable, but we have already
handled that case by ignoring unreachable call for contract calls.
@celinval celinval force-pushed the issue-136925-const-contract branch from 4f62bc2 to 3feac59 Compare April 8, 2025 17:49
Copy link
Member

@RalfJung RalfJung left a comment

Choose a reason for hiding this comment

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

Just a nit

@RalfJung
Copy link
Member

RalfJung commented Apr 9, 2025

LGTM from a const-eval perspective. I can't say anything about the compiler/ changes though.

@tautschnig
Copy link

Just confirming that all the contracts proposed in #136578 pass with the latest set of changes in this PR.

@celinval
Copy link
Contributor Author

@compiler-errors, any chance you can take a look at this PR?

@oli-obk
Copy link
Contributor

oli-obk commented Apr 14, 2025

@bors r=compiler-errors,oli-obk,RalfJung

@bors
Copy link
Collaborator

bors commented Apr 14, 2025

📌 Commit 13f1c84 has been approved by compiler-errors,oli-obk,RalfJung

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 14, 2025
Zalathar added a commit to Zalathar/rust that referenced this pull request Apr 15, 2025
…ct, r=compiler-errors,oli-obk,RalfJung

Enable contracts for const functions

Use `const_eval_select!()` macro to enable contract checking only at runtime. The existing contract logic relies on closures, which are not supported in constant functions.

This commit also removes one level of indirection for ensures clauses since we no longer build a closure around the ensures predicate.

Resolves rust-lang#136925

**Call-out:** This is still a draft PR since CI is broken due to a new warning message for unreachable code when the bottom of the function is indeed unreachable. It's not clear to me why the warning wasn't triggered before.

r? `@compiler-errors`
bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 15, 2025
Rollup of 12 pull requests

Successful merges:

 - rust-lang#138374 (Enable contracts for const functions)
 - rust-lang#138380 (ci: add runners for vanilla LLVM 20)
 - rust-lang#138393 (Allow const patterns of matches to contain pattern types)
 - rust-lang#139393 (rustdoc-json: Output target feature information)
 - rust-lang#139517 (std: sys: process: uefi: Use NULL stdin by default)
 - rust-lang#139554 (std: add Output::exit_ok)
 - rust-lang#139745 (Avoid unused clones in `Cloned<I>` and `Copied<I>`)
 - rust-lang#139757 (opt-dist: use executable-extension for host llvm-profdata)
 - rust-lang#139778 (Add test for issue 34834)
 - rust-lang#139783 (Use `compiletest-ignore-dir` for bootstrap self-tests)
 - rust-lang#139789 (do not unnecessarily leak auto traits in item bounds)
 - rust-lang#139791 (drop global where-bounds before merging candidates)

r? `@ghost`
`@rustbot` modify labels: rollup
jieyouxu added a commit to jieyouxu/rust that referenced this pull request Apr 15, 2025
…ct, r=compiler-errors,oli-obk,RalfJung

Enable contracts for const functions

Use `const_eval_select!()` macro to enable contract checking only at runtime. The existing contract logic relies on closures, which are not supported in constant functions.

This commit also removes one level of indirection for ensures clauses since we no longer build a closure around the ensures predicate.

Resolves rust-lang#136925

**Call-out:** This is still a draft PR since CI is broken due to a new warning message for unreachable code when the bottom of the function is indeed unreachable. It's not clear to me why the warning wasn't triggered before.

r? ``@compiler-errors``
bors added a commit to rust-lang-ci/rust that referenced this pull request Apr 15, 2025
Rollup of 17 pull requests

Successful merges:

 - rust-lang#138374 (Enable contracts for const functions)
 - rust-lang#138380 (ci: add runners for vanilla LLVM 20)
 - rust-lang#138393 (Allow const patterns of matches to contain pattern types)
 - rust-lang#139517 (std: sys: process: uefi: Use NULL stdin by default)
 - rust-lang#139554 (std: add Output::exit_ok)
 - rust-lang#139660 (compiletest: Add an experimental new executor to replace libtest)
 - rust-lang#139669 (Overhaul `AssocItem`)
 - rust-lang#139671 (Proc macro span API redesign: Replace proc_macro::SourceFile by Span::{file, local_file})
 - rust-lang#139750 (std/thread: Use default stack size from menuconfig for NuttX)
 - rust-lang#139772 (Remove `hir::Map`)
 - rust-lang#139785 (Let CStrings be either 1 or 2 byte aligned.)
 - rust-lang#139789 (do not unnecessarily leak auto traits in item bounds)
 - rust-lang#139791 (drop global where-bounds before merging candidates)
 - rust-lang#139798 (normalize: prefer `ParamEnv` over `AliasBound` candidates)
 - rust-lang#139822 (Fix: Map EOPNOTSUPP to ErrorKind::Unsupported on Unix)
 - rust-lang#139833 (Fix some HIR pretty-printing problems)
 - rust-lang#139836 (Basic tests of MPMC receiver cloning)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 380ad1b into rust-lang:master Apr 15, 2025
6 checks passed
@rustbot rustbot added this to the 1.88.0 milestone Apr 15, 2025
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Apr 15, 2025
Rollup merge of rust-lang#138374 - celinval:issue-136925-const-contract, r=compiler-errors,oli-obk,RalfJung

Enable contracts for const functions

Use `const_eval_select!()` macro to enable contract checking only at runtime. The existing contract logic relies on closures, which are not supported in constant functions.

This commit also removes one level of indirection for ensures clauses since we no longer build a closure around the ensures predicate.

Resolves rust-lang#136925

**Call-out:** This is still a draft PR since CI is broken due to a new warning message for unreachable code when the bottom of the function is indeed unreachable. It's not clear to me why the warning wasn't triggered before.

r? ```@compiler-errors```
github-actions bot pushed a commit to model-checking/verify-rust-std that referenced this pull request Apr 19, 2025
…ct, r=compiler-errors,oli-obk,RalfJung

Enable contracts for const functions

Use `const_eval_select!()` macro to enable contract checking only at runtime. The existing contract logic relies on closures, which are not supported in constant functions.

This commit also removes one level of indirection for ensures clauses since we no longer build a closure around the ensures predicate.

Resolves rust-lang#136925

**Call-out:** This is still a draft PR since CI is broken due to a new warning message for unreachable code when the bottom of the function is indeed unreachable. It's not clear to me why the warning wasn't triggered before.

r? ```@compiler-errors```
github-actions bot pushed a commit to model-checking/verify-rust-std that referenced this pull request Apr 19, 2025
Rollup of 17 pull requests

Successful merges:

 - rust-lang#138374 (Enable contracts for const functions)
 - rust-lang#138380 (ci: add runners for vanilla LLVM 20)
 - rust-lang#138393 (Allow const patterns of matches to contain pattern types)
 - rust-lang#139517 (std: sys: process: uefi: Use NULL stdin by default)
 - rust-lang#139554 (std: add Output::exit_ok)
 - rust-lang#139660 (compiletest: Add an experimental new executor to replace libtest)
 - rust-lang#139669 (Overhaul `AssocItem`)
 - rust-lang#139671 (Proc macro span API redesign: Replace proc_macro::SourceFile by Span::{file, local_file})
 - rust-lang#139750 (std/thread: Use default stack size from menuconfig for NuttX)
 - rust-lang#139772 (Remove `hir::Map`)
 - rust-lang#139785 (Let CStrings be either 1 or 2 byte aligned.)
 - rust-lang#139789 (do not unnecessarily leak auto traits in item bounds)
 - rust-lang#139791 (drop global where-bounds before merging candidates)
 - rust-lang#139798 (normalize: prefer `ParamEnv` over `AliasBound` candidates)
 - rust-lang#139822 (Fix: Map EOPNOTSUPP to ErrorKind::Unsupported on Unix)
 - rust-lang#139833 (Fix some HIR pretty-printing problems)
 - rust-lang#139836 (Basic tests of MPMC receiver cloning)

r? `@ghost`
`@rustbot` modify labels: rollup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Contract cannot be applied to const functions
9 participants