-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
[DRAFT] #[contracts::requires(...)] #128045
base: master
Are you sure you want to change the base?
Conversation
…nd an intrinsic for checking it. Known issues: * My original intent, as described in the MCP (rust-lang/compiler-team#759) was to have a rustc-prefixed attribute namespace (like rustc_contracts::requires). But I could not get things working when I tried to do rewriting via a rustc-prefixed builtin attribute-macro. So for now it is called `contracts::requires`. * The macro is currently expanding to a `core::intrinsics::contract_check` path. I suspect that the expansion will actually want to be conditional based on whether it is expanding within std or outside of std. * The macro itself is very naive; it assumes the first occurrence of `{ ... }` in the token-stream will be the function body (and injects the intrinsic call there). I suspect there are some contracts like `const { ... }` that might mess that up. * The emitted abort message when a contract fails is just "contract failure". Obviously the message should include more details, e.g. what function failed, which contract-expression failed and (maybe) what value caused the failure.
r? @fee1-dead rustbot has assigned @fee1-dead. Use |
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt |
Please add a ui test with an attribute proc-macro aux build that conflicts with |
register( | ||
sym::contracts_requires, | ||
SyntaxExtensionKind::Attr(Box::new(contracts::ExpandRequires)), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This macro should be able to use register_attr!
above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if we actually can, if we want to support arbitrary syntax within the contracts::require(...)
-- doesn't register_attr!
mandate that the requires expression conform to ast::MetaItem
, which imposes restrictions on what the syntax can be, i.e. x > 0
wouldn't work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a closer look at this, and this is very unfortunate. I don't believe the current builtin macro setup allows for "path segments"-like namespacing (like rustc_contracts::require
). I've tried to change the builtin macro support to allow multiple "segments" via SmallVec<[Symbol; 2]>
, but as one would expect, that change kept propagating outwards to attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry for my delay in responding here.
@jieyouxu is exactly right.
specifically, register_attr!
expands to a SyntaxExtensionKind::LegacyAttr
, which is where the conformance to ast::MetaItem
is enforced IIRC.
The plan here to support code snippets like x > 0
in a contract form means that we cannot conform to ast::MetaItem
.
(In theory I could try to extend the register_attr!
macro to support expansion to non LegacyAttr
. Is that what you are asking for, @petrochenkov ?)
*/ | ||
#[cfg(not(bootstrap))] | ||
#[unstable(feature = "rustc_contracts", issue = "none")] | ||
pub use crate::contracts; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding experimental stuff to the prelude is probably premature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah that's probably true.
(context: an earlier version of this branch had named this module rustc_contracts
instead of contracts
, in order to enable #[rustc_contract::requires(...)
. but for some reason our handling of rustc_
-prefixed attributes made that not work at all, and so I fell back on this approach. But you are right that it doesn't need to be in the prelude.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding macros to the prelude will likely break things since name resolution for macros AFAIK does not de-prioritize unstable macros until name resolution for normal items. Can this be changed to require an explicit import?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes I'll remove the change to the prelude.
r? compiler |
|
||
pub struct ExpandRequires; | ||
|
||
impl AttrProcMacro for ExpandRequires { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense for this to be a no-op if contract option is set to none
?
#![allow(internal_features)] | ||
#![feature(rustc_attrs, rustc_contracts, core_intrinsics)] | ||
|
||
#[contracts::requires(x > 0)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do like contracts::
, but it just occurred to me that it could conflict with the crate contracts.
r? compiler |
I don't know that part of the compiler r? @petrochenkov would you like to review this? |
No. |
r? compiler |
I'll ask T-compiler for another suitable reviewer to take a look at the HIR/MIR parts of the PR, or take over the review. In the mean time, I'll also roll a T-libs reviewer for the libs part. r? jieyouxu |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HIR and MIR changes lgtm
@@ -333,6 +333,12 @@ impl<'tcx> MirPass<'tcx> for LowerIntrinsics { | |||
}); | |||
terminator.kind = TerminatorKind::Goto { target }; | |||
} | |||
sym::contract_check => { | |||
if tcx.sess.opts.unstable_opts.contract_checking.no_checks() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the no_checks
method tripped me up a bit. Slight preference for not having it at all and using an exhaustive match here instead
I took a closer look at the
Unfortunately, I don't have good solutions for your problems. So I'm also going to reroll in the hopes that other reviewers could help you out here with better ideas... @oli-obk Oli, would you like to take over the review, or should we try to find someone else who might have a better idea of how to work around these issues? |
// NOTE: this will break if you have a `{ ... }` syntactically prior to the fn body, | ||
// e.g. if a `const { ... }` can occur in a where clause. We need to do something | ||
// smarter for injecting this code into the right place. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using register_attr
will resolve this, as you get access to an https://doc.rust-lang.org/nightly/nightly-rustc/rustc_expand/base/enum.Annotatable.html directly, so you can easily find the body and create wrappers for it and such. It's how we handle tests and benchmarks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I've tried this myself too, but doesn't register_attr
expect either a function or something that has a expand
signature of
fn expand(
&self,
ecx: &mut ExtCtxt<'_>,
span: Span,
meta_item: &ast::MetaItem,
item: Annotatable,
)
Specifically, ast::MetaItem
the annotation here limits what syntax is permitted within a hypothetical #[contracts::requires(<requires_expr>)]
? It would fix the { ... }
breaking issue, but then wouldn't it introduce a new <requires_expr>
can no longer be a complex expression issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm... true... but if we want to permit expressions there we should teach NestedItem
about it anyway (gated appropriately that it won't be permitted without feature gates even behind #[cfg(FALSE)]
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm.
So... rather than trying to build this atop Attr
(which is oriented around TokenTree to TokenTree transformations) they way that I have done here, ...
... it sounds like you are encouraging that I try to extend the functionality of the expanders we feed into LegacyAttr
(namely functions that implement the MultiItemModifier
trait, such as the signature @jieyouxu wrote above), which will then involve expanding the definition of ast::MetaItem
(or one of the types it uses, such as NestedMetaItem
) to now be able to take as inputs ... token trees? (or ... asts? is that distinction meaningful here?)
Do I have that right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, we already support #[foo = bar]
where bar
is an arbitrary expression, we just error on it in ast lowering. Seems fine to teach that to parse #[foo(someexpr)]
, too. But I'd want a @petrochenkov vibe check on that before touching it
Since oli is on leave, I'll try to find you another suitable reviewer. |
This PR is going to need a more suitable reviewer who's more familiar with attribute handling, and possibly another reviewer who's familiar with the HIR/MIR changes, so nominating for T-compiler discussion to find more suitable reviewer(s). The current challenge seems to be that existing attribute handling only supports a "single path segment", i.e. @rustbot labels +I-compiler-nominated (Also see previous reviewer request) |
I agree that the compiler team should discuss the best way to change the compiler to support the attributes described here, So I'm going to leave that nomination label on it. In addition, i really should have changed the tags and metadata to reflect that this is no longer "ready for review" -- the review feedback has made it clear that this should not land as-is. So I will also fix that now. @rustbot label: +S-waiting-on-author -S-waiting-on-review |
Discussed in t-compiler meeting on Zulip. This deserve a dedicated design meeting. @pnkfelix when you have a chance, could you file it up with a bit of context? thanks @rustbot label -I-compiler-nominated |
cc #128044
Initial go at contract support: attribute syntax for preconditions, and an intrinsic for checking it.
Known issues:
My original intent, as described in the MCP (Contracts: Experimental attributes and language intrinsics compiler-team#759) was to have a rustc-prefixed attribute namespace (like rustc_contracts::requires). But I could not get things working when I tried to do rewriting via a rustc-prefixed builtin attribute-macro. So for now it is called
contracts::requires
.The macro is currently expanding to a
core::intrinsics::contract_check
path. I suspect that the expansion will actually want to be conditional based on whether it is expanding within std or outside of std.The macro itself is very naive; it assumes the first occurrence of
{ ... }
in the token-stream will be the function body (and injects the intrinsic call there). I suspect there are some constructs likeconst { ... }
that might mess that up if they occur in certain contexts (I'm thinking first and foremost ofwhere
-clauses here)..The emitted abort message when a contract fails is just "contract failure". Obviously the message should include more details, e.g. what function failed, which contract-expression failed and (maybe) what value caused the failure.