Skip to content

Conversation

@celinval
Copy link
Contributor

Description of changes:

Introduce a derive macro that allows users to annotate their structs and enums with #[derive(kani::Arbitray)]. This will allow users to easily generate implementation of the Arbitrary types for their custom structs and enums when their arbitrary value is a combination of its fields arbitrary values.

Resolved issues:

Resolves #726

Related RFC:

Call-outs:

  • Since we don't publish our crates yet, users will have to use cfg_attr to actually use the derive in production code. I.e.:
#[cfg_attr(kani, derive(kani::Arbitrary))]
  • I will add documentation as a follow up PR.
  • I made a tiny change to our build script to print user friendly errors instead of json ones.

Testing:

  • How is this change tested? New tests

  • Is this a refactor change? No

Checklist

  • Each commit message has a non-empty body, explaining why the change was made
  • Methods or procedures are documented
  • Regression or unit tests are included, or existing tests cover the modified code
  • My PR is restricted to a single feature or bugfix

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

@celinval celinval requested a review from a team as a code owner December 16, 2022 23:08
@tedinski
Copy link
Contributor

I want to make it clear I don't object, but do we want to duplicate stuff like this ourselves versus directing people towards Bolero and bolero-generator-derive?

@celinval
Copy link
Contributor Author

I see your point, but I don't think we should have a hard dependency on bolero. We could think about merging the implementations into a separate crate though.

Copy link
Contributor

@zhassan-aws zhassan-aws left a comment

Choose a reason for hiding this comment

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

Very nice!

/// For named fields, this will generate: `Item { field1: kani::any(), field2: kani::any(), .. }`
/// For unnamed fields, this will generate: `Item (kani::any(), kani::any(), ..)`
/// For unit field, generate an empty initialization.
fn init_item(ident: &Ident, fields: &Fields) -> TokenStream {
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename to construct_symbolic_item or construct_nondet_item? The init gives an impression that it's getting initialized to some (concrete) value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is init_symbolic_item OK?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure.

/// Generate the body of the function `any()` for structures.
fn fn_any_enum(ident: &Ident, data: &DataEnum) -> TokenStream {
if data.variants.is_empty() {
abort!(Span::call_site(), "Cannot derive `Arbitrary` for `{}`", ident;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we make this case a no-op? It seems the compiler doesn't complain about zero-variant enums, e.g.:

#[derive(Debug)]
enum Foo {}

fn main() {}
$ rustc enum.rs && ./enum
$ 

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But invoking kany::any::<Foo>() would an error with a not so friendly message.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. I guess it doesn't matter much.

note = ident.span() => "`{}` has zero variants and cannot be instantiated", ident
);
}
let arms = data.variants.iter().enumerate().map(|(idx, variant)| {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I see what this is doing, but can you add to the function documentation an example of the output?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

});

quote! {
match kani::any() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this guaranteed to use a type that is wide enough for the number of variants?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Because the type will be decided based on the number of match conditions

@@ -0,0 +1,11 @@
// Copyright Kani Contributors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Check that Kani can automatically derive an empty Arbitrary enum.
Copy link
Contributor

Choose a reason for hiding this comment

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

"cannot" instead of "can"? But see my comment on making those a no-op.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess it can now.


Checking harness check_enum_wrapper...

SUCCESS\
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you relying on this being reported as "UNREACHABLE" if the variant is not possible? If so, you might want to use the brand new cover macro instead :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. I was thinking about that while writing all these tests. =)

fn check_arbitrary_point() {
let point: Point<i32, i8> = kani::any();
if kani::any() {
assert!(point.x >= 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

Might want to use cover instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! I will rewrite them.

Copy link
Contributor

@zhassan-aws zhassan-aws left a comment

Choose a reason for hiding this comment

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

Thanks!

@celinval celinval merged commit 245db28 into model-checking:main Dec 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create custom derive macro for Arbitrary type.

3 participants