Skip to content
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

Add a hidden trait to discourage manual impls of BitFlags #261

Merged
merged 7 commits into from Oct 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/bitflags_trait.rs
@@ -1,6 +1,10 @@
use core as _core;
#[doc(hidden)]
pub trait ImplementedByBitFlagsMacro {}

pub trait BitFlags {
/// A trait that is automatically implemented for all bitflags.
///
/// It should not be implemented manually.
pub trait BitFlags: ImplementedByBitFlagsMacro {
type Bits;
/// Returns an empty set of flags.
fn empty() -> Self;
Expand All @@ -10,7 +14,7 @@ pub trait BitFlags {
fn bits(&self) -> Self::Bits;
/// Convert from underlying bit representation, unless that
/// representation contains bits that do not correspond to a flag.
fn from_bits(bits: Self::Bits) -> _core::option::Option<Self>
fn from_bits(bits: Self::Bits) -> Option<Self>
where Self: Sized;
/// Convert from underlying bit representation, dropping any bits
/// that do not correspond to flags.
Expand Down
7 changes: 7 additions & 0 deletions src/lib.rs
Expand Up @@ -286,6 +286,11 @@ pub use bitflags_trait::BitFlags;

mod bitflags_trait;

#[doc(hidden)]
pub mod __private {
pub use crate::bitflags_trait::ImplementedByBitFlagsMacro;
}

/// The macro used to generate the flag structure.
///
/// See the [crate level docs](../bitflags/index.html) for complete documentation.
Expand Down Expand Up @@ -885,6 +890,8 @@ macro_rules! __impl_bitflags {
$BitFlags::set(self, other, value)
}
}

impl $crate::__private::ImplementedByBitFlagsMacro for $BitFlags {}
};

// Every attribute that the user writes on a const is applied to the
Expand Down
25 changes: 25 additions & 0 deletions tests/compile-fail/cfg/multi.rs
@@ -0,0 +1,25 @@
#[macro_use]
extern crate bitflags;

// NOTE: Ideally this would work, but our treatment of CFGs
// assumes flags may be missing but not redefined
bitflags! {
pub struct Flags: u32 {
#[cfg(target_os = "linux")]
const FOO = 1;
#[cfg(not(target_os = "linux"))]
const FOO = 2;
}
}

fn main() {
#[cfg(target_os = "linux")]
{
assert_eq!(1, Flags::FOO.bits());
}

#[cfg(not(target_os = "linux"))]
{
assert_eq!(1, Flags::FOO.bits());
}
}
35 changes: 35 additions & 0 deletions tests/compile-fail/cfg/multi.stderr.beta
@@ -0,0 +1,35 @@
error[E0428]: the name `FOO` is defined multiple times
--> $DIR/multi.rs:6:1
|
6 | / bitflags! {
7 | | pub struct Flags: u32 {
8 | | #[cfg(target_os = "linux")]
9 | | const FOO = 1;
... |
12 | | }
13 | | }
| | ^
| | |
| |_`FOO` redefined here
| previous definition of the value `FOO` here
|
= note: `FOO` must be defined only once in the value namespace of this trait
= note: this error originates in the macro `__impl_bitflags` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0428]: the name `FOO` is defined multiple times
--> $DIR/multi.rs:6:1
|
6 | / bitflags! {
7 | | pub struct Flags: u32 {
8 | | #[cfg(target_os = "linux")]
9 | | const FOO = 1;
... |
12 | | }
13 | | }
| | ^
| | |
| |_`FOO` redefined here
| previous definition of the value `FOO` here
|
= note: `FOO` must be defined only once in the value namespace of this trait
= note: this error originates in the macro `__impl_all_bitflags` (in Nightly builds, run with -Z macro-backtrace for more info)
4 changes: 2 additions & 2 deletions tests/compile-fail/non_integer_base/all_defined.stderr.beta
Expand Up @@ -10,7 +10,7 @@ error[E0308]: mismatched types
121 | | }
| |_^ expected struct `MyInt`, found integer
|
= note: this error originates in the macro `__impl_all_bitflags` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `__impl_bitflags` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
--> $DIR/all_defined.rs:115:1
Expand All @@ -24,4 +24,4 @@ error[E0308]: mismatched types
121 | | }
| |_^ expected struct `MyInt`, found integer
|
= note: this error originates in the macro `__impl_bitflags` (in Nightly builds, run with -Z macro-backtrace for more info)
= note: this error originates in the macro `__impl_all_bitflags` (in Nightly builds, run with -Z macro-backtrace for more info)
65 changes: 65 additions & 0 deletions tests/compile-fail/trait/custom_impl.rs
@@ -0,0 +1,65 @@
use bitflags::BitFlags;

pub struct BootlegFlags(u32);

impl BitFlags for BootlegFlags {
type Bits = u32;

fn empty() -> Self {
unimplemented!()
}

fn all() -> Self {
unimplemented!()
}

fn bits(&self) -> u32 {
unimplemented!()
}

fn from_bits(_: u32) -> Option<BootlegFlags> {
unimplemented!()
}

fn from_bits_truncate(_: u32) -> BootlegFlags {
unimplemented!()
}

unsafe fn from_bits_unchecked(_: u32) -> BootlegFlags {
unimplemented!()
}

fn is_empty(&self) -> bool {
unimplemented!()
}

fn is_all(&self) -> bool {
unimplemented!()
}

fn intersects(&self, _: BootlegFlags) -> bool {
unimplemented!()
}

fn contains(&self, _: BootlegFlags) -> bool {
unimplemented!()
}

fn insert(&mut self, _: BootlegFlags) {
unimplemented!()
}

fn remove(&mut self, _: BootlegFlags) {
unimplemented!()
}

fn toggle(&mut self, _: BootlegFlags) {
unimplemented!()
}

fn set(&mut self, _: BootlegFlags, value: bool) {
unimplemented!()
}
}

fn main() { }
11 changes: 11 additions & 0 deletions tests/compile-fail/trait/custom_impl.stderr.beta
@@ -0,0 +1,11 @@
error[E0277]: the trait bound `BootlegFlags: ImplementedByBitFlagsMacro` is not satisfied
--> $DIR/custom_impl.rs:5:6
|
5 | impl BitFlags for BootlegFlags {
| ^^^^^^^^ the trait `ImplementedByBitFlagsMacro` is not implemented for `BootlegFlags`
|
note: required by a bound in `BitFlags`
--> $DIR/bitflags_trait.rs:7:21
|
7 | pub trait BitFlags: ImplementedByBitFlagsMacro {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `BitFlags`
2 changes: 1 addition & 1 deletion tests/compile-fail/visibility/private_field.stderr.beta
Expand Up @@ -7,4 +7,4 @@ error[E0616]: field `bits` of struct `Flags1` is private
help: a method `bits` also exists, call it with parentheses
|
12 | let flag1 = example::Flags1::FLAG_A.bits();
| ^^
| ++
23 changes: 23 additions & 0 deletions tests/compile-pass/cfg/nested-value.rs
@@ -0,0 +1,23 @@
#[macro_use]
extern crate bitflags;

bitflags! {
pub struct Flags: u32 {
const FOO = {
#[cfg(target_os = "linux")] { 1 }
#[cfg(not(target_os = "linux"))] { 2 }
};
}
}

fn main() {
#[cfg(target_os = "linux")]
{
assert_eq!(1, Flags::FOO.bits());
}

#[cfg(not(target_os = "linux"))]
{
assert_eq!(2, Flags::FOO.bits());
}
}
22 changes: 22 additions & 0 deletions tests/compile-pass/trait/precedence.rs
@@ -0,0 +1,22 @@
use bitflags::{bitflags, BitFlags};

bitflags! {
struct Flags: u32 {
const A = 0b00000001;
}
}

impl From<u32> for Flags {
fn from(v: u32) -> Flags {
Flags::from_bits_truncate(v)
}
}

fn all_from_trait<F: BitFlags>() {
let _ = F::all();
}

fn main() {
all_from_trait::<Flags>();
let _ = Flags::all();
}
45 changes: 45 additions & 0 deletions tests/compile.rs
@@ -1,4 +1,5 @@
use std::{
env,
fs,
ffi::OsStr,
io,
Expand All @@ -12,7 +13,14 @@ fn fail() {
prepare_stderr_files("tests/compile-fail").unwrap();

let t = trybuild::TestCases::new();

t.compile_fail("tests/compile-fail/**/*.rs");

// `trybuild` will run its tests on `drop`
// We want to get a chance to use its output first
drop(t);

overwrite_stderr_files("tests/compile-fail").unwrap();
}

#[test]
Expand Down Expand Up @@ -50,6 +58,43 @@ fn prepare_stderr_files(path: impl AsRef<Path>) -> io::Result<()> {
Ok(())
}

// If we want to overwrite the expected compiler output then rename it
// to use our `.stderr.beta` convention. Otherwise the renamed file won't
// actually get picked up on the next run
fn overwrite_stderr_files(path: impl AsRef<Path>) -> io::Result<()> {
if env::var("TRYBUILD").ok().filter(|o| o == "overwrite").is_some() {
for entry in WalkDir::new(path) {
let entry = entry?;

// Look for any `.stderr` files and rename them to `.stderr.beta`
// If there's an existing `.beta` file then we want to remove it
if entry.path().extension().and_then(OsStr::to_str) == Some("stderr") {
let renamed = entry.path().with_extension("stderr.beta");

if renamed.exists() {
remove_beta_stderr(&renamed)?;
}

rename_beta_stderr(entry.path(), renamed)?;
}
}
}

Ok(())
}

#[rustversion::beta]
fn remove_beta_stderr(path: impl AsRef<Path>) -> io::Result<()> {
fs::remove_file(path)?;

Ok(())
}

#[rustversion::not(beta)]
fn remove_beta_stderr(_: impl AsRef<Path>) -> io::Result<()> {
Ok(())
}

#[rustversion::beta]
fn rename_beta_stderr(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
fs::copy(from, to)?;
Expand Down