Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: rust-lang/rust
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: pnkfelix/rust
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: rustc-contracts-just-req-and-ens
Choose a head ref
Checking mergeability… Don’t worry, you can still create the pull request.
  • 6 commits
  • 65 files changed
  • 1 contributor

Commits on Dec 2, 2024

  1. contracts core intrinsics. these are hooks to 1. control whether cont…

    …ract checks are run, and 2. allow 3rd party tools to intercept and reintepret the results of running contracts.
    pnkfelix committed Dec 2, 2024
    Copy the full SHA
    de62380 View commit details
  2. contracts: added lang items that act as hooks for rustc-injected code…

    … to invoke.
    
    see test for an example of the kind of injected code that is anticipated here.
    pnkfelix committed Dec 2, 2024
    Copy the full SHA
    be969ff View commit details

Commits on Dec 3, 2024

  1. intermediate step: express contracts as part of function header and l…

    …ower it into the previously added contract lang items.
    pnkfelix committed Dec 3, 2024
    Copy the full SHA
    3726c14 View commit details
  2. attribute-based contract syntax that desugars into the internal AST e…

    …xtensions added earlier.
    pnkfelix committed Dec 3, 2024
    Copy the full SHA
    1270eeb View commit details
  3. demonstrate how to capture state at precondition time and feed into p…

    …ostcondition predicate.
    pnkfelix committed Dec 3, 2024
    Copy the full SHA
    5bc520a View commit details
  4. separate feature gates for the internal machinery that should never b…

    …e user exposed versus the interface we want to ship externally eventually.
    pnkfelix committed Dec 3, 2024
    Copy the full SHA
    547668c View commit details
Showing with 1,070 additions and 49 deletions.
  1. +8 −1 compiler/rustc_ast/src/ast.rs
  2. +20 −3 compiler/rustc_ast/src/mut_visit.rs
  3. +25 −10 compiler/rustc_ast/src/visit.rs
  4. +18 −4 compiler/rustc_ast_lowering/src/expr.rs
  5. +101 −4 compiler/rustc_ast_lowering/src/item.rs
  6. +16 −0 compiler/rustc_ast_lowering/src/lib.rs
  7. +7 −5 compiler/rustc_ast_passes/src/ast_validation.rs
  8. +2 −0 compiler/rustc_ast_passes/src/feature_gate.rs
  9. +26 −5 compiler/rustc_ast_pretty/src/pprust/state/item.rs
  10. +1 −0 compiler/rustc_borrowck/src/type_check/mod.rs
  11. +1 −0 compiler/rustc_builtin_macros/src/alloc_error_handler.rs
  12. +171 −0 compiler/rustc_builtin_macros/src/contracts.rs
  13. +1 −0 compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
  14. +1 −0 compiler/rustc_builtin_macros/src/global_allocator.rs
  15. +5 −0 compiler/rustc_builtin_macros/src/lib.rs
  16. +1 −0 compiler/rustc_builtin_macros/src/test_harness.rs
  17. +4 −0 compiler/rustc_codegen_ssa/src/mir/rvalue.rs
  18. +1 −1 compiler/rustc_const_eval/src/check_consts/check.rs
  19. +10 −0 compiler/rustc_const_eval/src/interpret/machine.rs
  20. +1 −0 compiler/rustc_const_eval/src/interpret/operator.rs
  21. +1 −0 compiler/rustc_feature/src/builtin_attrs.rs
  22. +6 −0 compiler/rustc_feature/src/unstable.rs
  23. +2 −0 compiler/rustc_hir/src/hir.rs
  24. +3 −0 compiler/rustc_hir/src/lang_items.rs
  25. +21 −0 compiler/rustc_hir_analysis/src/check/intrinsic.rs
  26. +2 −0 compiler/rustc_hir_typeck/src/expr.rs
  27. +1 −0 compiler/rustc_lint/src/builtin.rs
  28. +1 −1 compiler/rustc_lint/src/early.rs
  29. +1 −0 compiler/rustc_middle/src/mir/pretty.rs
  30. +3 −0 compiler/rustc_middle/src/mir/syntax.rs
  31. +1 −0 compiler/rustc_middle/src/mir/tcx.rs
  32. +1 −1 compiler/rustc_mir_dataflow/src/move_paths/builder.rs
  33. +1 −0 compiler/rustc_mir_transform/src/gvn.rs
  34. +1 −0 compiler/rustc_mir_transform/src/known_panics_lint.rs
  35. +11 −0 compiler/rustc_mir_transform/src/lower_intrinsics.rs
  36. +1 −0 compiler/rustc_mir_transform/src/promote_consts.rs
  37. +1 −1 compiler/rustc_mir_transform/src/validate.rs
  38. +35 −2 compiler/rustc_parse/src/parser/generics.rs
  39. +6 −4 compiler/rustc_parse/src/parser/item.rs
  40. +2 −1 compiler/rustc_resolve/src/def_collector.rs
  41. +5 −3 compiler/rustc_resolve/src/late.rs
  42. +3 −3 compiler/rustc_resolve/src/late/diagnostics.rs
  43. +6 −0 compiler/rustc_session/src/config/cfg.rs
  44. +2 −0 compiler/rustc_session/src/options.rs
  45. +5 −0 compiler/rustc_session/src/parse.rs
  46. +4 −0 compiler/rustc_session/src/session.rs
  47. +1 −0 compiler/rustc_smir/src/rustc_smir/convert/mir.rs
  48. +3 −0 compiler/rustc_span/src/hygiene.rs
  49. +11 −0 compiler/rustc_span/src/symbol.rs
  50. +3 −0 compiler/stable_mir/src/mir/body.rs
  51. +35 −0 library/core/src/contracts.rs
  52. +32 −0 library/core/src/intrinsics.rs
  53. +5 −0 library/core/src/lib.rs
  54. +26 −0 library/core/src/macros/mod.rs
  55. +44 −0 tests/ui/contracts/contract-attributes-nest.rs
  56. +42 −0 tests/ui/contracts/contract-attributes-tail.rs
  57. +27 −0 tests/ui/contracts/contract-captures-via-closure-copy.rs
  58. +23 −0 tests/ui/contracts/contract-captures-via-closure-noncopy.rs
  59. +26 −0 tests/ui/contracts/contract-captures-via-closure-noncopy.stderr
  60. +44 −0 tests/ui/contracts/internal_machinery/contract-ast-extensions-nest.rs
  61. +42 −0 tests/ui/contracts/internal_machinery/contract-ast-extensions-tail.rs
  62. +23 −0 tests/ui/contracts/internal_machinery/contract-intrinsics.rs
  63. +48 −0 tests/ui/contracts/internal_machinery/contract-lang-items.rs
  64. +23 −0 tests/ui/contracts/internal_machinery/internal-feature-gating.rs
  65. +66 −0 tests/ui/contracts/internal_machinery/internal-feature-gating.stderr
9 changes: 8 additions & 1 deletion compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
@@ -3246,11 +3246,18 @@ pub struct Impl {
pub items: ThinVec<P<AssocItem>>,
}

#[derive(Clone, Encodable, Decodable, Debug, Default)]
pub struct FnContract {
pub requires: Option<P<Expr>>,
pub ensures: Option<P<Expr>>,
}

#[derive(Clone, Encodable, Decodable, Debug)]
pub struct Fn {
pub defaultness: Defaultness,
pub generics: Generics,
pub sig: FnSig,
pub contract: Option<P<FnContract>>,
pub body: Option<P<Block>>,
}

@@ -3548,7 +3555,7 @@ mod size_asserts {
static_assert_size!(Block, 32);
static_assert_size!(Expr, 72);
static_assert_size!(ExprKind, 40);
static_assert_size!(Fn, 160);
static_assert_size!(Fn, 168);
static_assert_size!(ForeignItem, 88);
static_assert_size!(ForeignItemKind, 16);
static_assert_size!(GenericArg, 24);
23 changes: 20 additions & 3 deletions compiler/rustc_ast/src/mut_visit.rs
Original file line number Diff line number Diff line change
@@ -119,6 +119,10 @@ pub trait MutVisitor: Sized {
walk_flat_map_item(self, i)
}

fn visit_contract(&mut self, c: &mut P<FnContract>) {
walk_contract(self, c);
}

fn visit_fn_decl(&mut self, d: &mut P<FnDecl>) {
walk_fn_decl(self, d);
}
@@ -898,6 +902,16 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) {
}
}

fn walk_contract<T: MutVisitor>(vis: &mut T, contract: &mut P<FnContract>) {
let FnContract { requires, ensures } = contract.deref_mut();
if let Some(pred) = requires {
vis.visit_expr(pred);
}
if let Some(pred) = ensures {
vis.visit_expr(pred);
}
}

fn walk_fn_decl<T: MutVisitor>(vis: &mut T, decl: &mut P<FnDecl>) {
let FnDecl { inputs, output } = decl.deref_mut();
inputs.flat_map_in_place(|param| vis.flat_map_param(param));
@@ -1100,8 +1114,9 @@ impl WalkItemKind for ItemKind {
ItemKind::Const(item) => {
visit_const_item(item, vis);
}
ItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
ItemKind::Fn(box Fn { defaultness, generics, sig, contract, body }) => {
visit_defaultness(vis, defaultness);
if let Some(contract) = contract { vis.visit_contract(contract) };
vis.visit_fn(FnKind::Fn(sig, generics, body), span, id);
}
ItemKind::Mod(safety, mod_kind) => {
@@ -1206,8 +1221,9 @@ impl WalkItemKind for AssocItemKind {
AssocItemKind::Const(item) => {
visit_const_item(item, visitor);
}
AssocItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
AssocItemKind::Fn(box Fn { defaultness, generics, sig, contract, body }) => {
visit_defaultness(visitor, defaultness);
if let Some(contract) = contract { visitor.visit_contract(contract); }
visitor.visit_fn(FnKind::Fn(sig, generics, body), span, id);
}
AssocItemKind::Type(box TyAlias {
@@ -1311,8 +1327,9 @@ impl WalkItemKind for ForeignItemKind {
visitor.visit_ty(ty);
visit_opt(expr, |expr| visitor.visit_expr(expr));
}
ForeignItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
ForeignItemKind::Fn(box Fn { defaultness, generics, sig, contract, body }) => {
visit_defaultness(visitor, defaultness);
if let Some(contract) = contract { visitor.visit_contract(contract); }
visitor.visit_fn(FnKind::Fn(sig, generics, body), span, id);
}
ForeignItemKind::TyAlias(box TyAlias {
35 changes: 25 additions & 10 deletions compiler/rustc_ast/src/visit.rs
Original file line number Diff line number Diff line change
@@ -66,7 +66,7 @@ impl BoundKind {
#[derive(Copy, Clone, Debug)]
pub enum FnKind<'a> {
/// E.g., `fn foo()`, `fn foo(&self)`, or `extern "Abi" fn foo()`.
Fn(FnCtxt, Ident, &'a FnSig, &'a Visibility, &'a Generics, Option<&'a Block>),
Fn(FnCtxt, Ident, &'a FnSig, &'a Visibility, &'a Generics, Option<&'a FnContract>, Option<&'a Block>),

/// E.g., `|x, y| body`.
Closure(&'a ClosureBinder, &'a Option<CoroutineKind>, &'a FnDecl, &'a Expr),
@@ -75,7 +75,7 @@ pub enum FnKind<'a> {
impl<'a> FnKind<'a> {
pub fn header(&self) -> Option<&'a FnHeader> {
match *self {
FnKind::Fn(_, _, sig, _, _, _) => Some(&sig.header),
FnKind::Fn(_, _, sig, _, _, _, _) => Some(&sig.header),
FnKind::Closure(..) => None,
}
}
@@ -89,7 +89,7 @@ impl<'a> FnKind<'a> {

pub fn decl(&self) -> &'a FnDecl {
match self {
FnKind::Fn(_, _, sig, _, _, _) => &sig.decl,
FnKind::Fn(_, _, sig, _, _, _, _) => &sig.decl,
FnKind::Closure(_, _, decl, _) => decl,
}
}
@@ -188,6 +188,9 @@ pub trait Visitor<'ast>: Sized {
fn visit_closure_binder(&mut self, b: &'ast ClosureBinder) -> Self::Result {
walk_closure_binder(self, b)
}
fn visit_contract(&mut self, c: &'ast FnContract) -> Self::Result {
walk_contract(self, c)
}
fn visit_where_predicate(&mut self, p: &'ast WherePredicate) -> Self::Result {
walk_where_predicate(self, p)
}
@@ -359,8 +362,8 @@ impl WalkItemKind for ItemKind {
try_visit!(visitor.visit_ty(ty));
visit_opt!(visitor, visit_expr, expr);
}
ItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
let kind = FnKind::Fn(FnCtxt::Free, *ident, sig, vis, generics, body.as_deref());
ItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => {
let kind = FnKind::Fn(FnCtxt::Free, *ident, sig, vis, generics, contract.as_deref(), body.as_deref());
try_visit!(visitor.visit_fn(kind, *span, *id));
}
ItemKind::Mod(_unsafety, mod_kind) => match mod_kind {
@@ -695,8 +698,8 @@ impl WalkItemKind for ForeignItemKind {
try_visit!(visitor.visit_ty(ty));
visit_opt!(visitor, visit_expr, expr);
}
ForeignItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, generics, body.as_deref());
ForeignItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => {
let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, generics, contract.as_deref(), body.as_deref());
try_visit!(visitor.visit_fn(kind, span, id));
}
ForeignItemKind::TyAlias(box TyAlias {
@@ -784,6 +787,17 @@ pub fn walk_closure_binder<'a, V: Visitor<'a>>(
V::Result::output()
}

pub fn walk_contract<'a, V:Visitor<'a>>(visitor: &mut V, c: &'a FnContract) -> V::Result {
let FnContract { requires, ensures } = c;
if let Some(pred) = requires {
visitor.visit_expr(pred);
}
if let Some(pred) = ensures {
visitor.visit_expr(pred);
}
V::Result::output()
}

pub fn walk_where_predicate<'a, V: Visitor<'a>>(
visitor: &mut V,
predicate: &'a WherePredicate,
@@ -829,11 +843,12 @@ pub fn walk_fn_decl<'a, V: Visitor<'a>>(

pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>) -> V::Result {
match kind {
FnKind::Fn(_ctxt, _ident, FnSig { header, decl, span: _ }, _vis, generics, body) => {
FnKind::Fn(_ctxt, _ident, FnSig { header, decl, span: _ }, _vis, generics, contract, body) => {
// Identifier and visibility are visited as a part of the item.
try_visit!(visitor.visit_fn_header(header));
try_visit!(visitor.visit_generics(generics));
try_visit!(walk_fn_decl(visitor, decl));
visit_opt!(visitor, visit_contract, contract);
visit_opt!(visitor, visit_block, body);
}
FnKind::Closure(binder, _coroutine_kind, decl, body) => {
@@ -859,9 +874,9 @@ impl WalkItemKind for AssocItemKind {
try_visit!(visitor.visit_ty(ty));
visit_opt!(visitor, visit_expr, expr);
}
AssocItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
AssocItemKind::Fn(box Fn { defaultness: _, generics, sig, contract, body }) => {
let kind =
FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, body.as_deref());
FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, contract.as_deref(), body.as_deref());
try_visit!(visitor.visit_fn(kind, span, id));
}
AssocItemKind::Type(box TyAlias {
22 changes: 18 additions & 4 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
@@ -295,8 +295,22 @@ impl<'hir> LoweringContext<'_, 'hir> {
hir::ExprKind::Continue(self.lower_jump_destination(e.id, *opt_label))
}
ExprKind::Ret(e) => {
let e = e.as_ref().map(|x| self.lower_expr(x));
hir::ExprKind::Ret(e)
let mut e = e.as_ref().map(|x| self.lower_expr(x));
if let Some(Some((span, fresh_ident))) = self.contract
.as_ref()
.map(|c| {
c.ensures.as_ref().map(|e|(e.expr.span, e.fresh_ident))
})
{
let checker_fn = self.expr_ident(span, fresh_ident.0, fresh_ident.2);
let args = if let Some(e) = e {
std::slice::from_ref(e)
} else {
std::slice::from_ref(self.expr_unit(span))
};
e = Some(self.expr_call(span, checker_fn, args));
}
hir::ExprKind::Ret(e)
}
ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()),
ExprKind::Become(sub_expr) => {
@@ -2035,7 +2049,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
self.arena.alloc(self.expr_call_mut(span, e, args))
}

fn expr_call_lang_item_fn_mut(
pub(super) fn expr_call_lang_item_fn_mut(
&mut self,
span: Span,
lang_item: hir::LangItem,
@@ -2045,7 +2059,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
self.expr_call_mut(span, path, args)
}

fn expr_call_lang_item_fn(
pub(super) fn expr_call_lang_item_fn(
&mut self,
span: Span,
lang_item: hir::LangItem,
105 changes: 101 additions & 4 deletions compiler/rustc_ast_lowering/src/item.rs
Original file line number Diff line number Diff line change
@@ -207,8 +207,37 @@ impl<'hir> LoweringContext<'_, 'hir> {
sig: FnSig { decl, header, span: fn_sig_span },
generics,
body,
contract,
..
}) => {
if let Some(contract) = contract {
assert!(self.contract.is_none());
let requires = contract.requires.clone();
let ensures = contract.ensures.clone();
let ensures = if let Some(ens) = ensures {
// FIXME: this needs to be a fresh (or illegal) identifier to prevent
// accidental capture of a parameter or global variable.
let checker_ident: Ident = Ident::from_str_and_span("__ensures_checker", ens.span);
let (checker_pat, checker_hir_id) = self.pat_ident_binding_mode_mut(
ens.span,
checker_ident,
hir::BindingMode::NONE,
);

Some(crate::FnContractLoweringEnsures {
expr: ens,
fresh_ident: (checker_ident, checker_pat, checker_hir_id),
})
} else {
None
};

self.contract.replace(crate::FnContractLoweringInfo {
span,
requires,
ensures,
});
}
self.with_new_scopes(*fn_sig_span, |this| {
// Note: we don't need to change the return type from `T` to
// `impl Future<Output = T>` here because lower_body
@@ -1096,10 +1125,78 @@ impl<'hir> LoweringContext<'_, 'hir> {
body: impl FnOnce(&mut Self) -> hir::Expr<'hir>,
) -> hir::BodyId {
self.lower_body(|this| {
(
this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x))),
body(this),
)

let params = this.arena.alloc_from_iter(decl.inputs.iter().map(|x| this.lower_param(x)));
let result = body(this);

let contract = this.contract.take();

// { body }
// ==>
// { rustc_contract_requires(PRECOND); { body } }
let result: hir::Expr<'hir> = if let Some(_contract) = contract {
let lit_unit = |this: &mut LoweringContext<'_, 'hir>| {
this.expr(_contract.span, hir::ExprKind::Tup(&[]))
};

let precond: hir::Stmt<'hir> = if let Some(req) = _contract.requires {
let lowered_req = this.lower_expr_mut(&req);
let precond = this.expr_call_lang_item_fn_mut(
req.span,
hir::LangItem::ContractCheckRequires,
&*arena_vec![this; lowered_req]
);
this.stmt_expr(req.span, precond)
} else {
let u = lit_unit(this);
this.stmt_expr(_contract.span, u)
};

let (postcond_checker, _opt_ident, result) = if let Some(ens) = _contract.ensures {
let crate::FnContractLoweringEnsures { expr: ens, fresh_ident } = ens;
let lowered_ens: hir::Expr<'hir> = this.lower_expr_mut(&ens);
let postcond_checker = this.expr_call_lang_item_fn(
ens.span,
hir::LangItem::ContractBuildCheckEnsures,
&*arena_vec![this; lowered_ens],
);
let checker_binding_pat = fresh_ident.1;
(this.stmt_let_pat(
None,
ens.span,
Some(postcond_checker),
this.arena.alloc(checker_binding_pat),
hir::LocalSource::Contract,
),
Some((fresh_ident.0, fresh_ident.2)),
{
let checker_fn = this.expr_ident(ens.span, fresh_ident.0, fresh_ident.2);
let span = this.mark_span_with_reason(
DesugaringKind::Contract,
ens.span,
None
);
this.expr_call_mut(span, checker_fn, std::slice::from_ref(this.arena.alloc(result)))
}
)
} else {
let u = lit_unit(this);
(this.stmt_expr(_contract.span, u),
None,
result)
};

let block = this.block_all(
_contract.span,
arena_vec![this; precond, postcond_checker],
Some(this.arena.alloc(result))
);
this.expr_block(block)
} else {
result
};

(params, result)
})
}

16 changes: 16 additions & 0 deletions compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
@@ -91,6 +91,19 @@ mod path;

rustc_fluent_macro::fluent_messages! { "../messages.ftl" }

#[derive(Debug, Clone)]
struct FnContractLoweringInfo<'hir> {
pub span: Span,
pub requires: Option<ast::ptr::P<ast::Expr>>,
pub ensures: Option<FnContractLoweringEnsures<'hir>>,
}

#[derive(Debug, Clone)]
struct FnContractLoweringEnsures<'hir> {
expr: ast::ptr::P<ast::Expr>,
fresh_ident: (Ident, hir::Pat<'hir>, HirId),
}

struct LoweringContext<'a, 'hir> {
tcx: TyCtxt<'hir>,
resolver: &'a mut ResolverAstLowering,
@@ -105,6 +118,8 @@ struct LoweringContext<'a, 'hir> {
/// Collect items that were created by lowering the current owner.
children: Vec<(LocalDefId, hir::MaybeOwner<'hir>)>,

contract: Option<FnContractLoweringInfo<'hir>>,

coroutine_kind: Option<hir::CoroutineKind>,

/// When inside an `async` context, this is the `HirId` of the
@@ -175,6 +190,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
bodies: Vec::new(),
attrs: SortedMap::default(),
children: Vec::default(),
contract: None,
current_hir_id_owner: hir::CRATE_OWNER_ID,
current_def_id_parent: CRATE_DEF_ID,
item_local_id_counter: hir::ItemLocalId::ZERO,
Loading