diff --git a/src/cargo/util/config/de.rs b/src/cargo/util/config/de.rs index 75653c0e447..4471727db0c 100644 --- a/src/cargo/util/config/de.rs +++ b/src/cargo/util/config/de.rs @@ -112,6 +112,10 @@ impl<'de, 'config> de::Deserializer<'de> for Deserializer<'config> { where V: de::Visitor<'de>, { + // Match on the magical struct name/field names that are passed in to + // detect when we're deserializing `Value`. + // + // See more comments in `value.rs` for the protocol used here. if name == value::NAME && fields == value::FIELDS { return visitor.visit_map(ValueDeserializer { hits: 0, de: self }); } @@ -340,6 +344,13 @@ impl<'de> de::SeqAccess<'de> for ConfigSeqAccess { } } +/// This is a deserializer that deserializes into a `Value` for +/// configuration. +/// +/// This is a special deserializer because it deserializes one of its struct +/// fields into the location that this configuration value was defined in. +/// +/// See more comments in `value.rs` for the protocol used here. struct ValueDeserializer<'config> { hits: u32, de: Deserializer<'config>, @@ -396,6 +407,9 @@ impl<'de, 'config> de::MapAccess<'de> for ValueDeserializer<'config> { } } +/// A deserializer which takes two values and deserializes into a tuple of those +/// two values. This is similar to types like `StrDeserializer` in upstream +/// serde itself. struct Tuple2Deserializer(T, U); impl<'de, T, U> de::Deserializer<'de> for Tuple2Deserializer diff --git a/src/cargo/util/config/value.rs b/src/cargo/util/config/value.rs index 893360baa56..0a428046259 100644 --- a/src/cargo/util/config/value.rs +++ b/src/cargo/util/config/value.rs @@ -1,3 +1,13 @@ +//! Deserialization of a `Value` type which tracks where it was deserialized +//! from. +//! +//! Often Cargo wants to report semantic error information or other sorts of +//! error information about configuration keys but it also may wish to indicate +//! as an error context where the key was defined as well (to help user +//! debugging). The `Value` type here can be used to deserialize a `T` value +//! from configuration, but also record where it was deserialized from when it +//! was read. + use crate::util::config::Config; use serde::de; use std::fmt; @@ -5,14 +15,37 @@ use std::marker; use std::mem; use std::path::{Path, PathBuf}; +/// A type which can be deserialized as a configuration value which records +/// where it was deserialized from. #[derive(Debug, PartialEq, Clone)] pub struct Value { + /// The inner value that was deserialized. pub val: T, + /// The location where `val` was defined in configuration (e.g. file it was + /// defined in, env var etc). pub definition: Definition, } pub type OptValue = Option>; +// Deserializing `Value` is pretty special, and serde doesn't have built-in +// support for this operation. To implement this we extend serde's "data model" +// a bit. We configure deserialization of `Value` to basically only work with +// our one deserializer using configuration. +// +// We define that `Value` deserialization asks the deserializer for a very +// special struct name and struct field names. In doing so the deserializer will +// recognize this and synthesize a magical value for the `definition` field when +// we deserialize it. This protocol is how we're able to have a channel of +// information flowing from the configuration deserializer into the +// deserialization implementation here. +// +// You'll want to also check out the implementation of `ValueDeserializer` in +// `de.rs`. Also note that the names below are intended to be invalid Rust +// identifiers to avoid how they might conflict with other valid structures. +// Finally the `definition` field is transmitted as a tuple of i32/string, which +// is effectively a tagged union of `Definition` itself. + pub(crate) const VALUE_FIELD: &str = "$__cargo_private_value"; pub(crate) const DEFINITION_FIELD: &str = "$__cargo_private_definition"; pub(crate) const NAME: &str = "$__cargo_private_Value";