Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
832fcfb
Introduce `DIBuilderBox`, an owning pointer to `DIBuilder`
Zalathar Jan 31, 2025
cd2af2d
Use `LLVMDIBuilderFinalize`
Zalathar Feb 1, 2025
878ab12
Use `LLVMDIBuilderCreateNameSpace`
Zalathar Feb 1, 2025
70d41bc
Use `LLVMDIBuilderCreateLexicalBlock`
Zalathar Feb 1, 2025
949b467
Use `LLVMDIBuilderCreateLexicalBlockFile`
Zalathar Feb 1, 2025
8ddd9c3
Use `LLVMDIBuilderCreateDebugLocation`
Zalathar Feb 1, 2025
5413d2b
Add FIXME for auditing optional parameters passed to DIBuilder
Zalathar Dec 7, 2024
c3f2930
Explain why (some) pointer/length strings are `*const c_uchar`
Zalathar Dec 12, 2024
510de59
rustdoc-json-types: Document that crate name isn't package name.
aDotInTheVoid Feb 1, 2025
f5d5210
rustdoc-book: Clean up section on `--output-format`
aDotInTheVoid Feb 1, 2025
2ea95f8
rustdoc: clean up a bunch of ts-expected-error declarations in main
notriddle Jan 29, 2025
6b016d7
Mark `std::fmt::from_fn` as `#[must_use]`
yotamofek Feb 3, 2025
bcb8565
Contracts core intrinsics.
pnkfelix Dec 2, 2024
777def8
contracts: added lang items that act as hooks for rustc-injected code…
pnkfelix Dec 2, 2024
38eff16
Express contracts as part of function header and lower it to the cont…
celinval Jan 9, 2025
4636dd9
Add tests for nested macro_rules edition behavior
ehuss Feb 3, 2025
ae7eff0
Desugars contract into the internal AST extensions
pnkfelix Dec 3, 2024
b279ff9
demonstrate how to capture state at precondition time and feed into …
pnkfelix Dec 3, 2024
6a6c6b8
Separate contract feature gates for the internal machinery
pnkfelix Dec 3, 2024
804cce4
Refactor contract builtin macro + error handling
celinval Jan 15, 2025
2bb1464
Improve contracts intrisics and remove wrapper function
celinval Jan 17, 2025
2c4923e
Update test output to include check_contracts cfg
celinval Jan 30, 2025
ddbf54b
Rename rustc_contract to contract
celinval Jan 31, 2025
53b8de1
bootstrap: add wrapper macros for `tracing`-gated tracing macros
jieyouxu Jan 31, 2025
d81701b
Rollup merge of #128045 - pnkfelix:rustc-contracts, r=oli-obk
fmease Feb 5, 2025
207777b
Rollup merge of #136263 - notriddle:notriddle/typescript2, r=fmease
fmease Feb 5, 2025
75989e9
Rollup merge of #136375 - Zalathar:llvm-di-builder, r=workingjubilee
fmease Feb 5, 2025
9830837
Rollup merge of #136392 - jieyouxu:wrap-tracing, r=onur-ozkan
fmease Feb 5, 2025
058ed2b
Rollup merge of #136396 - aDotInTheVoid:crate-isnt-package-whyyy, r=G…
fmease Feb 5, 2025
a6ebb87
Rollup merge of #136405 - aDotInTheVoid:unstable-doc, r=notriddle
fmease Feb 5, 2025
ba42006
Rollup merge of #136502 - yotamofek:pr/fmt-from-fn-must-use, r=dtolnay
fmease Feb 5, 2025
c20a58d
Rollup merge of #136509 - ehuss:nested-macro-rules-edition, r=jieyouxu
fmease Feb 5, 2025
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor contract builtin macro + error handling
Instead of parsing the different components of a function signature, eagerly look for either the `where` keyword or the function body. - Also address feedback to use `From` instead of `TryFrom` in cranelift contract and ubcheck codegen.
  • Loading branch information
celinval committed Feb 3, 2025
commit 804cce47d96d7b30f3798b51a1377c6697011c54
142 changes: 71 additions & 71 deletions compiler/rustc_builtin_macros/src/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,98 +35,98 @@ impl AttrProcMacro for ExpandEnsures {
}
}

fn expand_injecting_circa_where_clause(
/// Expand the function signature to include the contract clause.
///
/// The contracts clause will be injected before the function body and the optional where clause.
/// For that, we search for the body / where token, and invoke the `inject` callback to generate the
/// contract clause in the right place.
///
// FIXME: this kind of manual token tree munging does not have significant precedent among
// rustc builtin macros, probably because most builtin macros use direct AST manipulation to
// accomplish similar goals. But since our attributes need to take arbitrary expressions, and
// our attribute infrastructure does not yet support mixing a token-tree annotation with an AST
// annotated, we end up doing token tree manipulation.
fn expand_contract_clause(
ecx: &mut ExtCtxt<'_>,
attr_span: Span,
annotated: TokenStream,
inject: impl FnOnce(&mut Vec<TokenTree>) -> Result<(), ErrorGuaranteed>,
inject: impl FnOnce(&mut TokenStream) -> Result<(), ErrorGuaranteed>,
) -> Result<TokenStream, ErrorGuaranteed> {
let mut new_tts = Vec::with_capacity(annotated.len());
let mut new_tts = TokenStream::default();
let mut cursor = annotated.iter();

// Find the `fn name<G,...>(x:X,...)` and inject the AST contract forms right after
// the formal parameters (and return type if any).
while let Some(tt) = cursor.next() {
new_tts.push(tt.clone());
if let TokenTree::Token(tok, _) = tt
&& tok.is_ident_named(kw::Fn)
{
break;
}
let is_kw = |tt: &TokenTree, sym: Symbol| {
if let TokenTree::Token(token, _) = tt { token.is_ident_named(sym) } else { false }
};

// Find the `fn` keyword to check if this is a function.
if cursor
.find(|tt| {
new_tts.push_tree((*tt).clone());
is_kw(tt, kw::Fn)
})
.is_none()
{
return Err(ecx
.sess
.dcx()
.span_err(attr_span, "contract annotations can only be used on functions"));
}

// Found the `fn` keyword, now find the formal parameters.
//
// FIXME: can this fail if you have parentheticals in a generics list, like `fn foo<F: Fn(X) -> Y>` ?
while let Some(tt) = cursor.next() {
new_tts.push(tt.clone());

if let TokenTree::Delimited(_, _, token::Delimiter::Parenthesis, _) = tt {
break;
}
if let TokenTree::Token(token::Token { kind: token::TokenKind::Semi, .. }, _) = tt {
panic!("contract attribute applied to fn without parameter list.");
// Found the `fn` keyword, now find either the `where` token or the function body.
let next_tt = loop {
let Some(tt) = cursor.next() else {
return Err(ecx.sess.dcx().span_err(
attr_span,
"contract annotations is only supported in functions with bodies",
));
};
// If `tt` is the last element. Check if it is the function body.
if cursor.peek().is_none() {
if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = tt {
break tt;
} else {
return Err(ecx.sess.dcx().span_err(
attr_span,
"contract annotations is only supported in functions with bodies",
));
}
}
}

// There *might* be a return type declaration (and figuring out where that ends would require
// parsing an arbitrary type expression, e.g. `-> Foo<args ...>`
//
// Instead of trying to figure that out, scan ahead and look for the first occurence of a
// `where`, a `{ ... }`, or a `;`.
//
// FIXME: this might still fall into a trap for something like `-> Ctor<T, const { 0 }>`. I
// *think* such cases must be under a Delimited (e.g. `[T; { N }]` or have the braced form
// prefixed by e.g. `const`, so we should still be able to filter them out without having to
// parse the type expression itself. But rather than try to fix things with hacks like that,
// time might be better spent extending the attribute expander to suport tt-annotation atop
// ast-annotated, which would be an elegant way to sidestep all of this.
let mut opt_next_tt = cursor.next();
while let Some(next_tt) = opt_next_tt {
if let TokenTree::Token(tok, _) = next_tt
&& tok.is_ident_named(kw::Where)
{
break;
}
if let TokenTree::Delimited(_, _, token::Delimiter::Brace, _) = next_tt {
break;
}
if let TokenTree::Token(token::Token { kind: token::TokenKind::Semi, .. }, _) = next_tt {
break;
if is_kw(tt, kw::Where) {
break tt;
}

// for anything else, transcribe the tt and keep looking.
new_tts.push(next_tt.clone());
opt_next_tt = cursor.next();
}
new_tts.push_tree(tt.clone());
};

// At this point, we've transcribed everything from the `fn` through the formal parameter list
// and return type declaration, (if any), but `tt` itself has *not* been transcribed.
//
// Now inject the AST contract form.
//
// FIXME: this kind of manual token tree munging does not have significant precedent among
// rustc builtin macros, probably because most builtin macros use direct AST manipulation to
// accomplish similar goals. But since our attributes need to take arbitrary expressions, and
// our attribute infrastructure does not yet support mixing a token-tree annotation with an AST
// annotated, we end up doing token tree manipulation.
inject(&mut new_tts)?;

// Above we injected the internal AST requires/ensures contruct. Now copy over all the other
// Above we injected the internal AST requires/ensures construct. Now copy over all the other
// token trees.
if let Some(tt) = opt_next_tt {
new_tts.push(tt.clone());
}
new_tts.push_tree(next_tt.clone());
while let Some(tt) = cursor.next() {
new_tts.push(tt.clone());
new_tts.push_tree(tt.clone());
if cursor.peek().is_none()
&& !matches!(tt, TokenTree::Delimited(_, _, token::Delimiter::Brace, _))
{
return Err(ecx.sess.dcx().span_err(
attr_span,
"contract annotations is only supported in functions with bodies",
));
}
}

// Record the span as a contract attribute expansion.
// This is used later to stop users from using the extended syntax directly
// which is gated via `rustc_contracts_internals`.
ecx.psess().contract_attribute_spans.push(attr_span);

Ok(TokenStream::new(new_tts))
Ok(new_tts)
}

fn expand_requires_tts(
Expand All @@ -135,16 +135,16 @@ fn expand_requires_tts(
annotation: TokenStream,
annotated: TokenStream,
) -> Result<TokenStream, ErrorGuaranteed> {
expand_injecting_circa_where_clause(_ecx, attr_span, annotated, |new_tts| {
new_tts.push(TokenTree::Token(
expand_contract_clause(_ecx, attr_span, annotated, |new_tts| {
new_tts.push_tree(TokenTree::Token(
token::Token::from_ast_ident(Ident::new(kw::RustcContractRequires, attr_span)),
Spacing::Joint,
));
new_tts.push(TokenTree::Token(
new_tts.push_tree(TokenTree::Token(
token::Token::new(token::TokenKind::OrOr, attr_span),
Spacing::Alone,
));
new_tts.push(TokenTree::Delimited(
new_tts.push_tree(TokenTree::Delimited(
DelimSpan::from_single(attr_span),
DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden),
token::Delimiter::Parenthesis,
Expand All @@ -160,12 +160,12 @@ fn expand_ensures_tts(
annotation: TokenStream,
annotated: TokenStream,
) -> Result<TokenStream, ErrorGuaranteed> {
expand_injecting_circa_where_clause(_ecx, attr_span, annotated, |new_tts| {
new_tts.push(TokenTree::Token(
expand_contract_clause(_ecx, attr_span, annotated, |new_tts| {
new_tts.push_tree(TokenTree::Token(
token::Token::from_ast_ident(Ident::new(kw::RustcContractEnsures, attr_span)),
Spacing::Joint,
));
new_tts.push(TokenTree::Delimited(
new_tts.push_tree(TokenTree::Delimited(
DelimSpan::from_single(attr_span),
DelimSpacing::new(Spacing::JointHidden, Spacing::JointHidden),
token::Delimiter::Parenthesis,
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_codegen_cranelift/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,7 @@ fn codegen_stmt<'tcx>(
NullOp::UbChecks => {
let val = fx.tcx.sess.ub_checks();
let val = CValue::by_val(
fx.bcx.ins().iconst(types::I8, i64::try_from(val).unwrap()),
fx.bcx.ins().iconst(types::I8, i64::from(val)),
fx.layout_of(fx.tcx.types.bool),
);
lval.write_cvalue(fx, val);
Expand All @@ -877,7 +877,7 @@ fn codegen_stmt<'tcx>(
NullOp::ContractChecks => {
let val = fx.tcx.sess.contract_checks();
let val = CValue::by_val(
fx.bcx.ins().iconst(types::I8, i64::try_from(val).unwrap()),
fx.bcx.ins().iconst(types::I8, i64::from(val)),
fx.layout_of(fx.tcx.types.bool),
);
lval.write_cvalue(fx, val);
Expand Down
27 changes: 27 additions & 0 deletions tests/ui/contracts/contract-annotation-limitations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! Test for some of the existing limitations and the current error messages.
//! Some of these limitations may be removed in the future.

#![feature(rustc_contracts)]
#![allow(dead_code)]

/// Represent a 5-star system.
struct Stars(u8);

impl Stars {
fn is_valid(&self) -> bool {
self.0 <= 5
}
}

trait ParseStars {
#[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))]
//~^ ERROR contract annotations is only supported in functions with bodies
fn parse_string(input: String) -> Option<Stars>;

#[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))]
//~^ ERROR contract annotations is only supported in functions with bodies
fn parse<T>(input: T) -> Option<Stars> where T: for<'a> Into<&'a str>;
}

fn main() {
}
14 changes: 14 additions & 0 deletions tests/ui/contracts/contract-annotation-limitations.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: contract annotations is only supported in functions with bodies
--> $DIR/contract-annotation-limitations.rs:17:5
|
LL | #[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: contract annotations is only supported in functions with bodies
--> $DIR/contract-annotation-limitations.rs:21:5
|
LL | #[core::contracts::ensures(|ret| ret.is_none_or(Stars::is_valid))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors

70 changes: 70 additions & 0 deletions tests/ui/contracts/contract-attributes-generics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! Test that contracts can be applied to generic functions.

//@ revisions: unchk_pass chk_pass chk_fail_pre chk_fail_post chk_const_fail
//
//@ [unchk_pass] run-pass
//@ [chk_pass] run-pass
//
//@ [chk_fail_pre] run-fail
//@ [chk_fail_post] run-fail
//@ [chk_const_fail] run-fail
//
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
//
//@ [chk_pass] compile-flags: -Zcontract-checks=yes
//@ [chk_fail_pre] compile-flags: -Zcontract-checks=yes
//@ [chk_fail_post] compile-flags: -Zcontract-checks=yes
//@ [chk_const_fail] compile-flags: -Zcontract-checks=yes

#![feature(rustc_contracts)]

use std::ops::Sub;

/// Dummy fn contract that precondition fails for val < 0, and post-condition fail for val == 1
#[core::contracts::requires(val > 0u8.into())]
#[core::contracts::ensures(|ret| *ret > 0u8.into())]
fn decrement<T>(val: T) -> T
where T: PartialOrd + Sub<Output=T> + From<u8>
{
val - 1u8.into()
}

/// Create a structure that takes a constant parameter.
#[allow(dead_code)]
struct Capped<const MAX: usize>(usize);

/// Now declare a function to create stars which shouldn't exceed 5 stars.
// Add redundant braces to ensure the built-in macro can handle this syntax.
#[allow(unused_braces)]
#[core::contracts::requires(num <= 5)]
unsafe fn stars_unchecked(num: usize) -> Capped<{ 5 }> {
Capped(num)
}


fn main() {
check_decrement();
check_stars();
}

fn check_stars() {
// This should always pass.
let _ = unsafe { stars_unchecked(3) };

// This violates the contract.
#[cfg(any(unchk_pass, chk_const_fail))]
let _ = unsafe { stars_unchecked(10) };
}

fn check_decrement() {
// This should always pass
assert_eq!(decrement(10u8), 9u8);

// This should fail requires but pass with no contract check.
#[cfg(any(unchk_pass, chk_fail_pre))]
assert_eq!(decrement(-2i128), -3i128);

// This should fail ensures but pass with no contract check.
#[cfg(any(unchk_pass, chk_fail_post))]
assert_eq!(decrement(1i32), 0i32);
}
52 changes: 52 additions & 0 deletions tests/ui/contracts/disallow-contract-annotation-on-non-fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//! Checks for compilation errors related to adding contracts to non-function items.

#![feature(rustc_contracts)]
#![allow(dead_code)]

#[core::contracts::requires(true)]
//~^ ERROR contract annotations can only be used on functions
struct Dummy(usize);

#[core::contracts::ensures(|v| v == 100)]
//~^ ERROR contract annotations can only be used on functions
const MAX_VAL: usize = 100;

// FIXME: Improve the error message here. The macro thinks this is a function.
#[core::contracts::ensures(|v| v == 100)]
//~^ ERROR contract annotations is only supported in functions with bodies
type NewDummy = fn(usize) -> Dummy;

#[core::contracts::ensures(|v| v == 100)]
//~^ ERROR contract annotations is only supported in functions with bodies
const NEW_DUMMY_FN : fn(usize) -> Dummy = || { Dummy(0) };

#[core::contracts::requires(true)]
//~^ ERROR contract annotations can only be used on functions
impl Dummy {

// This should work
#[core::contracts::ensures(|ret| ret.0 == v)]
fn new(v: usize) -> Dummy {
Dummy(v)
}
}

#[core::contracts::ensures(|dummy| dummy.0 > 0)]
//~^ ERROR contract annotations can only be used on functions
impl From<usize> for Dummy {
// This should work.
#[core::contracts::ensures(|ret| ret.0 == v)]
fn from(value: usize) -> Self {
Dummy::new(value)
}
}

/// You should not be able to annotate a trait either.
#[core::contracts::requires(true)]
//~^ ERROR contract annotations can only be used on functions
pub trait DummyBuilder {
fn build() -> Dummy;
}

fn main() {
}
Loading