Skip to content

Commit

Permalink
subscriber: make PartialOrd & Ord impls more correct
Browse files Browse the repository at this point in the history
Currently, there are some minor issues with `Ord` and `PartialOrd` impls
in `tracing_subscriber::filter::env`:

- The `Directive` and `StaticDirective` types implement `PartialOrd`
  with an implementation that never returns `None`, and then have `Ord`
  implementations that call `partial_cmp` and `expect` that the returned
  value is `Some`. This isn't necessary.
- `field::Match` implements `PartialOrd` manually but derives `Ord`.
  Since these traits must agree, using the derived implementation for
  one but manually implementing the other is potentially incorrect (see
  #991).

This branch fixes these issues. I've moved actual comparison code from
`PartialOrd` impls for `Directive` and `StaticDirective` to their `Ord`
impls, and changed `PartialOrd::partial_cmp` for those types to call
`Ord::cmp` and wrap it in a `Some`, rather than having `Ord::cmp` call
`PartialOrd::partial_cmp` and unwrap it. This way, the fact that these
comparison impls can never return `None` is encoded at the type level,
rather than having an `expect` that will always succeed.

Additionally, I've added a manual impl of `Ord` for `field::Match` and
removed the derived impl. This should make Clippy happier.

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
  • Loading branch information
hawkw committed Sep 28, 2020
1 parent 216b98e commit 2f1fb1a
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 16 deletions.
21 changes: 13 additions & 8 deletions tracing-subscriber/src/filter/env/directive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ impl Default for Directive {

impl PartialOrd for Directive {
fn partial_cmp(&self, other: &Directive) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for Directive {
fn cmp(&self, other: &Directive) -> Ordering {
// We attempt to order directives by how "specific" they are. This
// ensures that we try the most specific directives first when
// attempting to match a piece of metadata.
Expand Down Expand Up @@ -321,7 +327,7 @@ impl PartialOrd for Directive {
}
}

Some(ordering)
ordering
}
}

Expand Down Expand Up @@ -502,8 +508,8 @@ impl Statics {
}
}

impl PartialOrd for StaticDirective {
fn partial_cmp(&self, other: &StaticDirective) -> Option<Ordering> {
impl Ord for StaticDirective {
fn cmp(&self, other: &StaticDirective) -> Ordering {
// We attempt to order directives by how "specific" they are. This
// ensures that we try the most specific directives first when
// attempting to match a piece of metadata.
Expand Down Expand Up @@ -542,14 +548,13 @@ impl PartialOrd for StaticDirective {
}
}

Some(ordering)
ordering
}
}

impl Ord for StaticDirective {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other)
.expect("StaticDirective::partial_cmp should define a total order")
impl PartialOrd for StaticDirective {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

Expand Down
20 changes: 12 additions & 8 deletions tracing-subscriber/src/filter/env/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::{
use super::{FieldMap, LevelFilter};
use tracing_core::field::{Field, Visit};

#[derive(Debug, Eq, PartialEq, Ord)]
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct Match {
pub(crate) name: String, // TODO: allow match patterns for names?
pub(crate) value: Option<ValueMatch>,
Expand Down Expand Up @@ -95,8 +95,8 @@ impl fmt::Display for Match {
}
}

impl PartialOrd for Match {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
impl Ord for Match {
fn cmp(&self, other: &Self) -> Ordering {
// Ordering for `Match` directives is based first on _whether_ a value
// is matched or not. This is semantically meaningful --- we would
// prefer to check directives that match values first as they are more
Expand All @@ -113,11 +113,15 @@ impl PartialOrd for Match {
// This ordering is no longer semantically meaningful but is necessary
// so that the directives can be stored in the `BTreeMap` in a defined
// order.
Some(
has_value
.then_with(|| self.name.cmp(&other.name))
.then_with(|| self.value.cmp(&other.value)),
)
has_value
.then_with(|| self.name.cmp(&other.name))
.then_with(|| self.value.cmp(&other.value)),
}
}

impl PartialOrd for Match {
fn partial_cmp(&self, other: &Self) -> Ordering {
Some(self.cmp(other))
}
}

Expand Down

0 comments on commit 2f1fb1a

Please sign in to comment.