Express contracts as part of function header and lower it to the contract lang items
includes post-developed commit: do not suggest internal-only keywords as corrections to parse failures. includes post-developed commit: removed tabs that creeped in into rustfmt tool source code. includes post-developed commit, placating rustfmt self dogfooding. includes post-developed commit: add backquotes to prevent markdown checking from trying to treat an attr as a markdown hyperlink/ includes post-developed commit: fix lowering to keep contracts from being erroneously inherited by nested bodies (like closures). Rebase Conflicts: - compiler/rustc_parse/src/parser/diagnostics.rs - compiler/rustc_parse/src/parser/item.rs - compiler/rustc_span/src/hygiene.rs Remove contracts keywords from diagnostic messages
This commit is contained in:
parent
777def87d5
commit
38eff16d0a
27 changed files with 405 additions and 17 deletions
|
@ -3348,11 +3348,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>>,
|
||||
}
|
||||
|
||||
|
@ -3650,7 +3657,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);
|
||||
|
|
|
@ -143,6 +143,10 @@ pub trait MutVisitor: Sized {
|
|||
walk_flat_map_assoc_item(self, i, ctxt)
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -958,13 +962,16 @@ fn walk_fn<T: MutVisitor>(vis: &mut T, kind: FnKind<'_>) {
|
|||
_ctxt,
|
||||
_ident,
|
||||
_vis,
|
||||
Fn { defaultness, generics, body, sig: FnSig { header, decl, span } },
|
||||
Fn { defaultness, generics, contract, body, sig: FnSig { header, decl, span } },
|
||||
) => {
|
||||
// Identifier and visibility are visited as a part of the item.
|
||||
visit_defaultness(vis, defaultness);
|
||||
vis.visit_fn_header(header);
|
||||
vis.visit_generics(generics);
|
||||
vis.visit_fn_decl(decl);
|
||||
if let Some(contract) = contract {
|
||||
vis.visit_contract(contract);
|
||||
}
|
||||
if let Some(body) = body {
|
||||
vis.visit_block(body);
|
||||
}
|
||||
|
@ -979,6 +986,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));
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
@ -800,6 +803,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,
|
||||
|
@ -862,12 +876,13 @@ pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>) -> V::Resu
|
|||
_ctxt,
|
||||
_ident,
|
||||
_vis,
|
||||
Fn { defaultness: _, sig: FnSig { header, decl, span: _ }, generics, body },
|
||||
Fn { defaultness: _, sig: FnSig { header, decl, span: _ }, 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!(visitor.visit_fn_decl(decl));
|
||||
visit_opt!(visitor, visit_contract, contract);
|
||||
visit_opt!(visitor, visit_block, body);
|
||||
}
|
||||
FnKind::Closure(binder, coroutine_kind, decl, body) => {
|
||||
|
|
|
@ -314,7 +314,20 @@ 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));
|
||||
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()),
|
||||
|
|
|
@ -207,9 +207,42 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
sig: FnSig { decl, header, span: fn_sig_span },
|
||||
generics,
|
||||
body,
|
||||
contract,
|
||||
..
|
||||
}) => {
|
||||
self.with_new_scopes(*fn_sig_span, |this| {
|
||||
assert!(this.contract.is_none());
|
||||
if let Some(contract) = contract {
|
||||
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) = this.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
|
||||
};
|
||||
|
||||
// Note: `with_new_scopes` will reinstall the outer
|
||||
// item's contract (if any) after its callback finishes.
|
||||
this.contract.replace(crate::FnContractLoweringInfo {
|
||||
span,
|
||||
requires,
|
||||
ensures,
|
||||
});
|
||||
}
|
||||
|
||||
// Note: we don't need to change the return type from `T` to
|
||||
// `impl Future<Output = T>` here because lower_body
|
||||
// only cares about the input argument patterns in the function
|
||||
|
@ -1054,10 +1087,81 @@ 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 opt_contract = this.contract.take();
|
||||
|
||||
// { body }
|
||||
// ==>
|
||||
// { rustc_contract_requires(PRECOND); { body } }
|
||||
let result: hir::Expr<'hir> = if let Some(contract) = opt_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, 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,
|
||||
),
|
||||
{
|
||||
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), 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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,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,
|
||||
|
@ -100,6 +113,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
|
||||
|
@ -148,6 +163,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,
|
||||
item_local_id_counter: hir::ItemLocalId::ZERO,
|
||||
ident_and_label_to_local_id: Default::default(),
|
||||
|
@ -834,12 +850,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
|||
let was_in_loop_condition = self.is_in_loop_condition;
|
||||
self.is_in_loop_condition = false;
|
||||
|
||||
let old_contract = self.contract.take();
|
||||
|
||||
let catch_scope = self.catch_scope.take();
|
||||
let loop_scope = self.loop_scope.take();
|
||||
let ret = f(self);
|
||||
self.catch_scope = catch_scope;
|
||||
self.loop_scope = loop_scope;
|
||||
|
||||
self.contract = old_contract;
|
||||
|
||||
self.is_in_loop_condition = was_in_loop_condition;
|
||||
|
||||
self.current_item = current_item;
|
||||
|
|
|
@ -917,7 +917,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
|
|||
walk_list!(self, visit_attribute, &item.attrs);
|
||||
return; // Avoid visiting again.
|
||||
}
|
||||
ItemKind::Fn(func @ box Fn { defaultness, generics: _, sig, body }) => {
|
||||
ItemKind::Fn(func @ box Fn { defaultness, generics: _, sig, contract: _, body }) => {
|
||||
self.check_defaultness(item.span, *defaultness);
|
||||
|
||||
let is_intrinsic =
|
||||
|
|
|
@ -650,13 +650,17 @@ impl<'a> State<'a> {
|
|||
attrs: &[ast::Attribute],
|
||||
func: &ast::Fn,
|
||||
) {
|
||||
let ast::Fn { defaultness, generics, sig, body } = func;
|
||||
let ast::Fn { defaultness, generics, sig, contract, body } = func;
|
||||
if body.is_some() {
|
||||
self.head("");
|
||||
}
|
||||
self.print_visibility(vis);
|
||||
self.print_defaultness(*defaultness);
|
||||
self.print_fn(&sig.decl, sig.header, Some(name), generics);
|
||||
if let Some(contract) = &contract {
|
||||
self.nbsp();
|
||||
self.print_contract(contract);
|
||||
}
|
||||
if let Some(body) = body {
|
||||
self.nbsp();
|
||||
self.print_block_with_attrs(body, attrs);
|
||||
|
@ -665,6 +669,21 @@ impl<'a> State<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn print_contract(&mut self, contract: &ast::FnContract) {
|
||||
if let Some(pred) = &contract.requires {
|
||||
self.word("rustc_requires");
|
||||
self.popen();
|
||||
self.print_expr(pred, FixupContext::default());
|
||||
self.pclose();
|
||||
}
|
||||
if let Some(pred) = &contract.ensures {
|
||||
self.word("rustc_ensures");
|
||||
self.popen();
|
||||
self.print_expr(pred, FixupContext::default());
|
||||
self.pclose();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn print_fn(
|
||||
&mut self,
|
||||
decl: &ast::FnDecl,
|
||||
|
|
|
@ -85,6 +85,7 @@ fn generate_handler(cx: &ExtCtxt<'_>, handler: Ident, span: Span, sig_span: Span
|
|||
defaultness: ast::Defaultness::Final,
|
||||
sig,
|
||||
generics: Generics::default(),
|
||||
contract: None,
|
||||
body,
|
||||
}));
|
||||
|
||||
|
|
|
@ -1034,6 +1034,7 @@ impl<'a> MethodDef<'a> {
|
|||
defaultness,
|
||||
sig,
|
||||
generics: fn_generics,
|
||||
contract: None,
|
||||
body: Some(body_block),
|
||||
})),
|
||||
tokens: None,
|
||||
|
|
|
@ -81,6 +81,7 @@ impl AllocFnFactory<'_, '_> {
|
|||
defaultness: ast::Defaultness::Final,
|
||||
sig,
|
||||
generics: Generics::default(),
|
||||
contract: None,
|
||||
body,
|
||||
}));
|
||||
let item = self.cx.item(
|
||||
|
|
|
@ -344,6 +344,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
|
|||
defaultness,
|
||||
sig,
|
||||
generics: ast::Generics::default(),
|
||||
contract: None,
|
||||
body: Some(main_body),
|
||||
}));
|
||||
|
||||
|
|
|
@ -2598,6 +2598,8 @@ pub enum LocalSource {
|
|||
/// A desugared `expr = expr`, where the LHS is a tuple, struct, array or underscore expression.
|
||||
/// The span is that of the `=` sign.
|
||||
AssignDesugar(Span),
|
||||
/// A contract `#[ensures(..)]` attribute injects a let binding for the check that runs at point of return.
|
||||
Contract,
|
||||
}
|
||||
|
||||
/// Hints at the original code for a `match _ { .. }`.
|
||||
|
|
|
@ -269,6 +269,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
// diverging expression (e.g. it arose from desugaring of `try { return }`),
|
||||
// we skip issuing a warning because it is autogenerated code.
|
||||
ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::TryBlock) => {}
|
||||
// Likewise, do not lint unreachable code injected via contracts desugaring.
|
||||
ExprKind::Call(..) if expr.span.is_desugaring(DesugaringKind::Contract) => {}
|
||||
ExprKind::Call(callee, _) => self.warn_if_unreachable(expr.hir_id, callee.span, "call"),
|
||||
ExprKind::MethodCall(segment, ..) => {
|
||||
self.warn_if_unreachable(expr.hir_id, segment.ident.span, "call")
|
||||
|
|
|
@ -297,6 +297,28 @@ impl<'a> Parser<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Parses a rustc-internal fn contract
|
||||
/// (`rustc_contract_requires(WWW) rustc_contract_ensures(ZZZ)`)
|
||||
pub(super) fn parse_contract(
|
||||
&mut self,
|
||||
) -> PResult<'a, Option<rustc_ast::ptr::P<ast::FnContract>>> {
|
||||
let requires = if self.eat_keyword_noexpect(exp!(RustcContractRequires).kw) {
|
||||
Some(self.parse_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ensures = if self.eat_keyword_noexpect(exp!(RustcContractEnsures).kw) {
|
||||
Some(self.parse_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if requires.is_none() && ensures.is_none() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(rustc_ast::ptr::P(ast::FnContract { requires, ensures })))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an optional where-clause.
|
||||
///
|
||||
/// ```ignore (only-for-syntax-highlight)
|
||||
|
|
|
@ -213,9 +213,12 @@ impl<'a> Parser<'a> {
|
|||
self.parse_use_item()?
|
||||
} else if self.check_fn_front_matter(check_pub, case) {
|
||||
// FUNCTION ITEM
|
||||
let (ident, sig, generics, body) =
|
||||
let (ident, sig, generics, contract, body) =
|
||||
self.parse_fn(attrs, fn_parse_mode, lo, vis, case)?;
|
||||
(ident, ItemKind::Fn(Box::new(Fn { defaultness: def_(), sig, generics, body })))
|
||||
(
|
||||
ident,
|
||||
ItemKind::Fn(Box::new(Fn { defaultness: def_(), sig, generics, contract, body })),
|
||||
)
|
||||
} else if self.eat_keyword(exp!(Extern)) {
|
||||
if self.eat_keyword(exp!(Crate)) {
|
||||
// EXTERN CRATE
|
||||
|
@ -2372,7 +2375,7 @@ impl<'a> Parser<'a> {
|
|||
sig_lo: Span,
|
||||
vis: &Visibility,
|
||||
case: Case,
|
||||
) -> PResult<'a, (Ident, FnSig, Generics, Option<P<Block>>)> {
|
||||
) -> PResult<'a, (Ident, FnSig, Generics, Option<P<FnContract>>, Option<P<Block>>)> {
|
||||
let fn_span = self.token.span;
|
||||
let header = self.parse_fn_front_matter(vis, case)?; // `const ... fn`
|
||||
let ident = self.parse_ident()?; // `foo`
|
||||
|
@ -2398,6 +2401,8 @@ impl<'a> Parser<'a> {
|
|||
// inside `parse_fn_body()`.
|
||||
let fn_params_end = self.prev_token.span.shrink_to_hi();
|
||||
|
||||
let contract = self.parse_contract()?;
|
||||
|
||||
generics.where_clause = self.parse_where_clause()?; // `where T: Ord`
|
||||
|
||||
// `fn_params_end` is needed only when it's followed by a where clause.
|
||||
|
@ -2409,7 +2414,7 @@ impl<'a> Parser<'a> {
|
|||
let body =
|
||||
self.parse_fn_body(attrs, &ident, &mut sig_hi, fn_parse_mode.req_body, fn_params_end)?;
|
||||
let fn_sig_span = sig_lo.to(sig_hi);
|
||||
Ok((ident, FnSig { header, decl, span: fn_sig_span }, generics, body))
|
||||
Ok((ident, FnSig { header, decl, span: fn_sig_span }, generics, contract, body))
|
||||
}
|
||||
|
||||
/// Provide diagnostics when function body is not found
|
||||
|
|
|
@ -108,6 +108,8 @@ pub enum TokenType {
|
|||
KwRef,
|
||||
KwReturn,
|
||||
KwReuse,
|
||||
KwRustcContractEnsures,
|
||||
KwRustcContractRequires,
|
||||
KwSafe,
|
||||
KwSelfUpper,
|
||||
KwStatic,
|
||||
|
@ -242,6 +244,8 @@ impl TokenType {
|
|||
KwRef,
|
||||
KwReturn,
|
||||
KwReuse,
|
||||
KwRustcContractEnsures,
|
||||
KwRustcContractRequires,
|
||||
KwSafe,
|
||||
KwSelfUpper,
|
||||
KwStatic,
|
||||
|
@ -314,6 +318,8 @@ impl TokenType {
|
|||
TokenType::KwRef => Some(kw::Ref),
|
||||
TokenType::KwReturn => Some(kw::Return),
|
||||
TokenType::KwReuse => Some(kw::Reuse),
|
||||
TokenType::KwRustcContractEnsures => Some(kw::RustcContractEnsures),
|
||||
TokenType::KwRustcContractRequires => Some(kw::RustcContractRequires),
|
||||
TokenType::KwSafe => Some(kw::Safe),
|
||||
TokenType::KwSelfUpper => Some(kw::SelfUpper),
|
||||
TokenType::KwStatic => Some(kw::Static),
|
||||
|
@ -544,6 +550,8 @@ macro_rules! exp {
|
|||
(Ref) => { exp!(@kw, Ref, KwRef) };
|
||||
(Return) => { exp!(@kw, Return, KwReturn) };
|
||||
(Reuse) => { exp!(@kw, Reuse, KwReuse) };
|
||||
(RustcContractEnsures) => { exp!(@kw, RustcContractEnsures, KwRustcContractEnsures) };
|
||||
(RustcContractRequires) => { exp!(@kw, RustcContractRequires, KwRustcContractRequires) };
|
||||
(Safe) => { exp!(@kw, Safe, KwSafe) };
|
||||
(SelfUpper) => { exp!(@kw, SelfUpper, KwSelfUpper) };
|
||||
(Static) => { exp!(@kw, Static, KwStatic) };
|
||||
|
|
|
@ -174,10 +174,13 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> {
|
|||
_ctxt,
|
||||
_ident,
|
||||
_vis,
|
||||
Fn { sig: FnSig { header, decl, span: _ }, generics, body, .. },
|
||||
Fn { sig: FnSig { header, decl, span: _ }, generics, contract, body, .. },
|
||||
) if let Some(coroutine_kind) = header.coroutine_kind => {
|
||||
self.visit_fn_header(header);
|
||||
self.visit_generics(generics);
|
||||
if let Some(contract) = contract {
|
||||
self.visit_contract(contract);
|
||||
}
|
||||
|
||||
// For async functions, we need to create their inner defs inside of a
|
||||
// closure to match their desugared representation. Besides that,
|
||||
|
|
|
@ -1019,7 +1019,7 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r
|
|||
// Create a label rib for the function.
|
||||
this.with_label_rib(RibKind::FnOrCoroutine, |this| {
|
||||
match fn_kind {
|
||||
FnKind::Fn(_, _, _, Fn { sig, generics, body, .. }) => {
|
||||
FnKind::Fn(_, _, _, Fn { sig, generics, contract, body, .. }) => {
|
||||
this.visit_generics(generics);
|
||||
|
||||
let declaration = &sig.decl;
|
||||
|
@ -1046,6 +1046,10 @@ impl<'ra: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'r
|
|||
},
|
||||
);
|
||||
|
||||
if let Some(contract) = contract {
|
||||
this.visit_contract(contract);
|
||||
}
|
||||
|
||||
if let Some(body) = body {
|
||||
// Ignore errors in function bodies if this is rustdoc
|
||||
// Be sure not to set this until the function signature has been resolved.
|
||||
|
|
|
@ -1163,6 +1163,8 @@ pub enum DesugaringKind {
|
|||
WhileLoop,
|
||||
/// `async Fn()` bound modifier
|
||||
BoundModifier,
|
||||
/// Calls to contract checks (`#[requires]` to precond, `#[ensures]` to postcond)
|
||||
Contract,
|
||||
}
|
||||
|
||||
impl DesugaringKind {
|
||||
|
@ -1179,6 +1181,7 @@ impl DesugaringKind {
|
|||
DesugaringKind::ForLoop => "`for` loop",
|
||||
DesugaringKind::WhileLoop => "`while` loop",
|
||||
DesugaringKind::BoundModifier => "trait bound modifier",
|
||||
DesugaringKind::Contract => "contract check",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,6 +118,8 @@ symbols! {
|
|||
MacroRules: "macro_rules",
|
||||
Raw: "raw",
|
||||
Reuse: "reuse",
|
||||
RustcContractEnsures: "rustc_contract_ensures",
|
||||
RustcContractRequires: "rustc_contract_requires",
|
||||
Safe: "safe",
|
||||
Union: "union",
|
||||
Yeet: "yeet",
|
||||
|
|
|
@ -113,8 +113,8 @@
|
|||
#![feature(bigint_helper_methods)]
|
||||
#![feature(bstr)]
|
||||
#![feature(bstr_internals)]
|
||||
#![feature(const_carrying_mul_add)]
|
||||
#![feature(closure_track_caller)]
|
||||
#![feature(const_carrying_mul_add)]
|
||||
#![feature(const_eval_select)]
|
||||
#![feature(core_intrinsics)]
|
||||
#![feature(coverage_attribute)]
|
||||
|
|
|
@ -362,18 +362,21 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
|
|||
defaultness: ld,
|
||||
sig: lf,
|
||||
generics: lg,
|
||||
contract: lc,
|
||||
body: lb,
|
||||
}),
|
||||
Fn(box ast::Fn {
|
||||
defaultness: rd,
|
||||
sig: rf,
|
||||
generics: rg,
|
||||
contract: rc,
|
||||
body: rb,
|
||||
}),
|
||||
) => {
|
||||
eq_defaultness(*ld, *rd)
|
||||
&& eq_fn_sig(lf, rf)
|
||||
&& eq_generics(lg, rg)
|
||||
&& eq_opt_fn_contract(lc, rc)
|
||||
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
|
||||
},
|
||||
(Mod(lu, lmk), Mod(ru, rmk)) => {
|
||||
|
@ -497,18 +500,21 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
|
|||
defaultness: ld,
|
||||
sig: lf,
|
||||
generics: lg,
|
||||
contract: lc,
|
||||
body: lb,
|
||||
}),
|
||||
Fn(box ast::Fn {
|
||||
defaultness: rd,
|
||||
sig: rf,
|
||||
generics: rg,
|
||||
contract: rc,
|
||||
body: rb,
|
||||
}),
|
||||
) => {
|
||||
eq_defaultness(*ld, *rd)
|
||||
&& eq_fn_sig(lf, rf)
|
||||
&& eq_generics(lg, rg)
|
||||
&& eq_opt_fn_contract(lc, rc)
|
||||
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
|
||||
},
|
||||
(
|
||||
|
@ -559,18 +565,21 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
|
|||
defaultness: ld,
|
||||
sig: lf,
|
||||
generics: lg,
|
||||
contract: lc,
|
||||
body: lb,
|
||||
}),
|
||||
Fn(box ast::Fn {
|
||||
defaultness: rd,
|
||||
sig: rf,
|
||||
generics: rg,
|
||||
contract: rc,
|
||||
body: rb,
|
||||
}),
|
||||
) => {
|
||||
eq_defaultness(*ld, *rd)
|
||||
&& eq_fn_sig(lf, rf)
|
||||
&& eq_generics(lg, rg)
|
||||
&& eq_opt_fn_contract(lc, rc)
|
||||
&& both(lb.as_ref(), rb.as_ref(), |l, r| eq_block(l, r))
|
||||
},
|
||||
(
|
||||
|
@ -653,6 +662,17 @@ pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
|
|||
&& eq_ext(&l.ext, &r.ext)
|
||||
}
|
||||
|
||||
pub fn eq_opt_fn_contract(l: &Option<P<FnContract>>, r: &Option<P<FnContract>>) -> bool {
|
||||
match (l, r) {
|
||||
(Some(l), Some(r)) => {
|
||||
eq_expr_opt(l.requires.as_ref(), r.requires.as_ref())
|
||||
&& eq_expr_opt(l.ensures.as_ref(), r.ensures.as_ref())
|
||||
}
|
||||
(None, None) => true,
|
||||
(Some(_), None) | (None, Some(_)) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
|
||||
over(&l.params, &r.params, eq_generic_param)
|
||||
&& over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
|
||||
|
|
44
tests/ui/contracts/contract-ast-extensions-nest.rs
Normal file
44
tests/ui/contracts/contract-ast-extensions-nest.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post
|
||||
//
|
||||
//@ [unchk_pass] run-pass
|
||||
//@ [unchk_fail_pre] run-pass
|
||||
//@ [unchk_fail_post] run-pass
|
||||
//@ [chk_pass] run-pass
|
||||
//
|
||||
//@ [chk_fail_pre] run-fail
|
||||
//@ [chk_fail_post] run-fail
|
||||
//
|
||||
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
|
||||
//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no
|
||||
//@ [unchk_fail_post] 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
|
||||
|
||||
#![feature(rustc_contracts)]
|
||||
|
||||
fn nest(x: Baz) -> i32
|
||||
rustc_contract_requires(|| x.baz > 0)
|
||||
rustc_contract_ensures(|ret| *ret > 100)
|
||||
{
|
||||
loop {
|
||||
return x.baz + 50;
|
||||
}
|
||||
}
|
||||
|
||||
struct Baz { baz: i32 }
|
||||
|
||||
const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 };
|
||||
#[cfg(any(unchk_fail_post, chk_fail_post))]
|
||||
const BAZ_FAIL_POST: Baz = Baz { baz: 10 };
|
||||
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
|
||||
const BAZ_FAIL_PRE: Baz = Baz { baz: -10 };
|
||||
|
||||
fn main() {
|
||||
assert_eq!(nest(BAZ_PASS_PRE_POST), 150);
|
||||
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
|
||||
nest(BAZ_FAIL_PRE);
|
||||
#[cfg(any(unchk_fail_post, chk_fail_post))]
|
||||
nest(BAZ_FAIL_POST);
|
||||
}
|
42
tests/ui/contracts/contract-ast-extensions-tail.rs
Normal file
42
tests/ui/contracts/contract-ast-extensions-tail.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
//@ revisions: unchk_pass unchk_fail_pre unchk_fail_post chk_pass chk_fail_pre chk_fail_post
|
||||
//
|
||||
//@ [unchk_pass] run-pass
|
||||
//@ [unchk_fail_pre] run-pass
|
||||
//@ [unchk_fail_post] run-pass
|
||||
//@ [chk_pass] run-pass
|
||||
//
|
||||
//@ [chk_fail_pre] run-fail
|
||||
//@ [chk_fail_post] run-fail
|
||||
//
|
||||
//@ [unchk_pass] compile-flags: -Zcontract-checks=no
|
||||
//@ [unchk_fail_pre] compile-flags: -Zcontract-checks=no
|
||||
//@ [unchk_fail_post] 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
|
||||
|
||||
#![feature(rustc_contracts)]
|
||||
|
||||
fn tail(x: Baz) -> i32
|
||||
rustc_contract_requires(|| x.baz > 0)
|
||||
rustc_contract_ensures(|ret| *ret > 100)
|
||||
{
|
||||
x.baz + 50
|
||||
}
|
||||
|
||||
struct Baz { baz: i32 }
|
||||
|
||||
const BAZ_PASS_PRE_POST: Baz = Baz { baz: 100 };
|
||||
#[cfg(any(unchk_fail_post, chk_fail_post))]
|
||||
const BAZ_FAIL_POST: Baz = Baz { baz: 10 };
|
||||
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
|
||||
const BAZ_FAIL_PRE: Baz = Baz { baz: -10 };
|
||||
|
||||
fn main() {
|
||||
assert_eq!(tail(BAZ_PASS_PRE_POST), 150);
|
||||
#[cfg(any(unchk_fail_pre, chk_fail_pre))]
|
||||
tail(BAZ_FAIL_PRE);
|
||||
#[cfg(any(unchk_fail_post, chk_fail_post))]
|
||||
tail(BAZ_FAIL_POST);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
//@ run-pass
|
||||
//@ compile-flags: -Zcontract-checks=yes
|
||||
#![feature(rustc_contracts)]
|
||||
|
||||
fn outer() -> i32
|
||||
rustc_contract_ensures(|ret| *ret > 0)
|
||||
{
|
||||
let inner_closure = || -> i32 { 0 };
|
||||
inner_closure();
|
||||
10
|
||||
}
|
||||
|
||||
fn main() {
|
||||
outer();
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
//@ run-pass
|
||||
//@ compile-flags: -Zcontract-checks=yes
|
||||
#![feature(rustc_contracts)]
|
||||
|
||||
struct Outer { outer: std::cell::Cell<i32> }
|
||||
|
||||
fn outer(x: Outer)
|
||||
rustc_contract_requires(|| x.outer.get() > 0)
|
||||
{
|
||||
let inner_closure = || { };
|
||||
x.outer.set(0);
|
||||
inner_closure();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
outer(Outer { outer: 1.into() });
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue