Skip to main content

lattice_config/
overrides.rs

1//! Layered option overrides (M.2.1).
2//!
3//! Modes (and other layer producers — buffer-local sets,
4//! modal-state hooks) contribute typed overrides via
5//! [`OptionOverride`]. The resolver in `lattice-config` walks
6//! the layers in priority order (`mode-architecture.md` §6.1)
7//! and picks the first non-empty value per option for scalars;
8//! collection-shaped options concatenate.
9//!
10//! ## Why this lives in `lattice-config`
11//!
12//! `Mode::options()` (in `lattice-mode`) returns an
13//! [`OptionOverrideSet`]. These types once lived in `lattice-mode`
14//! to break a `lattice-mode → lattice-config → lattice-core →
15//! lattice-mode` cycle; the M.4 dependency inversion removed
16//! `Document::modes` from `lattice-core`, retiring the cycle, so the
17//! layer inputs now sit beside the [`crate::Resolver`] and
18//! [`crate::ResolvedOptions`] they feed. `lattice-mode` re-exports
19//! them.
20//!
21//! ## Type-safe construction via `lattice-config`'s `overrides!`
22//!
23//! Modes don't construct [`OptionOverride`] directly with the
24//! erased [`OptionOverride::new`] API. Instead they use the
25//! `overrides!` macro from `lattice-config`, which has access
26//! to `OptionDecl` and emits compile-time-typed wrappers around
27//! [`OptionOverride::new`]:
28//!
29//! ```
30//! use lattice_config::{OptionOverrideSet, OverridePriority, Tabstop, Wrap};
31//!
32//! fn options() -> OptionOverrideSet {
33//!     lattice_config::overrides! {
34//!         Tabstop = 4,
35//!         #[priority(High)]
36//!         Wrap = false,
37//!     }
38//! }
39//!
40//! let set = options();
41//! assert_eq!(set.len(), 2);
42//! let wrap = set.iter().nth(1).unwrap();
43//! assert_eq!(wrap.downcast_value::<bool>(), Some(&false));
44//! assert_eq!(wrap.priority, OverridePriority::High);
45//! ```
46//!
47//! The macro asserts at compile time that each value matches its
48//! declaration's `Value` type. Direct [`OptionOverride::new`]
49//! is reserved for the WIT plugin adapter (M.10), where
50//! declarations are runtime data and TypeId is the only handle.
51
52use std::any::{Any, TypeId};
53use std::sync::Arc;
54
55use smallvec::SmallVec;
56
57/// Tie-break priority for two layer entries that target the
58/// same option.
59///
60/// Most modes use `Normal`; among `Normal`s the
61/// [`crate::Resolver`] prefers the higher layer (the mode registry
62/// orders minor-mode layers by activation, so last-activated wins)
63/// and `lattice-mode` emits a `ModeEvent::OptionConflict` event for
64/// visibility. `High` / `Low` are explicit overrides
65/// for modes that genuinely need to win or lose regardless of
66/// activation order (`read-only-mode` ⇒ `High` for
67/// `writable=false`).
68///
69/// See `mode-architecture.md` §6.2 for the conflict policy.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
71pub enum OverridePriority {
72    /// Loses to any `Normal` or `High` entry for the same option,
73    /// whatever its layer — a fallback a mode offers only if nobody
74    /// else has an opinion.
75    Low,
76    /// The default: resolved by layer order, then position in layer.
77    #[default]
78    Normal,
79    /// Beats every `Normal` / `Low` entry regardless of layer — except
80    /// a user's buffer-local (`:setlocal`) value, which outranks any
81    /// mode contribution at any priority.
82    High,
83}
84
85/// One option-value override from a single layer producer (a
86/// mode, a buffer-local set, a modal-state hook).
87///
88/// Identity is the `option_type_id` — the `TypeId` of the
89/// declaration type (e.g. `TypeId::of::<Tabstop>()`). The
90/// resolver looks up the option by this id; the typed downcast
91/// against the option's `Value` is performed when emitting the
92/// resolved cache.
93#[derive(Clone)]
94pub struct OptionOverride {
95    /// `TypeId` of the `OptionDecl` type this override targets.
96    /// `OptionDecl` lives in `lattice-config`; we don't have
97    /// access to the trait at this layer, so we identify by
98    /// `TypeId` and trust the macro / consumer to construct
99    /// type-correctly.
100    pub option_type_id: TypeId,
101    /// The override value, type-erased. Downcast at resolution
102    /// time to the declaration's `Value` type.
103    pub value: Arc<dyn Any + Send + Sync>,
104    /// Tie-break priority. See [`OverridePriority`].
105    pub priority: OverridePriority,
106}
107
108impl std::fmt::Debug for OptionOverride {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        f.debug_struct("OptionOverride")
111            .field("option_type_id", &self.option_type_id)
112            .field("priority", &self.priority)
113            .finish_non_exhaustive()
114    }
115}
116
117impl OptionOverride {
118    /// Erased constructor. The caller is responsible for
119    /// supplying a `value` of the type matching the declaration's
120    /// `Value` -- this contract is enforced by
121    /// `lattice-config`'s `overrides!` macro at compile time
122    /// (it generates a typed let-binding before the call). For
123    /// runtime / plugin construction, the caller is responsible
124    /// for honouring the contract.
125    pub fn new<V: Clone + Send + Sync + 'static>(option_type_id: TypeId, value: V) -> Self {
126        Self {
127            option_type_id,
128            value: Arc::new(value),
129            priority: OverridePriority::default(),
130        }
131    }
132
133    /// Same as [`Self::new`] but with explicit priority.
134    pub fn with_priority<V: Clone + Send + Sync + 'static>(
135        option_type_id: TypeId,
136        value: V,
137        priority: OverridePriority,
138    ) -> Self {
139        Self {
140            option_type_id,
141            value: Arc::new(value),
142            priority,
143        }
144    }
145
146    /// Promote `self` to a higher priority. Used by the
147    /// `overrides!` macro's priority-attribute branch.
148    pub fn at_priority(mut self, priority: OverridePriority) -> Self {
149        self.priority = priority;
150        self
151    }
152
153    /// Attempt to downcast the value to the requested type.
154    /// Returns `None` if the stored value doesn't match `V`.
155    pub fn downcast_value<V: 'static>(&self) -> Option<&V> {
156        self.value.downcast_ref::<V>()
157    }
158}
159
160/// A set of [`OptionOverride`]s contributed by one layer
161/// producer (typically the return value of `Mode::options()`).
162///
163/// `SmallVec` keeps the typical case (0-4 overrides per mode)
164/// inline; modes that contribute many overrides spill to the
165/// heap. Layer-input data, not hot-path read data — the
166/// resolver walks each set exactly once per
167/// `recompute_options` call.
168#[derive(Default, Clone)]
169pub struct OptionOverrideSet {
170    overrides: SmallVec<[OptionOverride; 4]>,
171}
172
173impl std::fmt::Debug for OptionOverrideSet {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        f.debug_struct("OptionOverrideSet")
176            .field("len", &self.overrides.len())
177            .finish_non_exhaustive()
178    }
179}
180
181impl OptionOverrideSet {
182    /// An empty set — what a mode that overrides nothing returns.
183    pub fn new() -> Self {
184        Self::default()
185    }
186
187    /// An empty set with room for `cap` overrides before spilling
188    /// past the inline capacity (4).
189    pub fn with_capacity(cap: usize) -> Self {
190        Self {
191            overrides: SmallVec::with_capacity(cap),
192        }
193    }
194
195    /// Append an override. Order within a set is preserved;
196    /// the resolver visits elements in this order when merging.
197    pub fn push(&mut self, ov: OptionOverride) {
198        self.overrides.push(ov);
199    }
200
201    /// Number of overrides in the set, duplicates included (two
202    /// entries for one option count twice).
203    pub fn len(&self) -> usize {
204        self.overrides.len()
205    }
206
207    /// `true` when the set contributes nothing.
208    pub fn is_empty(&self) -> bool {
209        self.overrides.is_empty()
210    }
211
212    /// The overrides in push order — the order the resolver visits
213    /// them in, so a later entry for the same option wins within the
214    /// set (priority permitting).
215    pub fn iter(&self) -> impl Iterator<Item = &OptionOverride> {
216        self.overrides.iter()
217    }
218}
219
220impl FromIterator<OptionOverride> for OptionOverrideSet {
221    fn from_iter<I: IntoIterator<Item = OptionOverride>>(iter: I) -> Self {
222        let mut set = Self::new();
223        for ov in iter {
224            set.push(ov);
225        }
226        set
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    struct OptionA;
235    struct OptionB;
236
237    #[test]
238    fn override_carries_typed_value() {
239        let ov = OptionOverride::new(TypeId::of::<OptionA>(), 42i64);
240        assert_eq!(ov.option_type_id, TypeId::of::<OptionA>());
241        assert_eq!(ov.priority, OverridePriority::Normal);
242        assert_eq!(ov.downcast_value::<i64>().copied(), Some(42));
243    }
244
245    #[test]
246    fn downcast_to_wrong_type_returns_none() {
247        let ov = OptionOverride::new(TypeId::of::<OptionA>(), 42i64);
248        assert!(ov.downcast_value::<bool>().is_none());
249    }
250
251    #[test]
252    fn override_targets_correct_type() {
253        let a = OptionOverride::new(TypeId::of::<OptionA>(), 1i64);
254        let b = OptionOverride::new(TypeId::of::<OptionB>(), 2i64);
255        assert_ne!(a.option_type_id, b.option_type_id);
256    }
257
258    #[test]
259    fn override_set_preserves_push_order() {
260        let mut set = OptionOverrideSet::new();
261        set.push(OptionOverride::new(TypeId::of::<OptionA>(), 1i64));
262        set.push(OptionOverride::new(TypeId::of::<OptionB>(), 2i64));
263        let collected: Vec<_> = set.iter().map(|o| o.option_type_id).collect();
264        assert_eq!(
265            collected,
266            vec![TypeId::of::<OptionA>(), TypeId::of::<OptionB>()]
267        );
268    }
269
270    #[test]
271    fn priority_ordering() {
272        assert!(OverridePriority::Low < OverridePriority::Normal);
273        assert!(OverridePriority::Normal < OverridePriority::High);
274    }
275
276    #[test]
277    fn with_priority_preserves_priority() {
278        let ov =
279            OptionOverride::with_priority(TypeId::of::<OptionA>(), true, OverridePriority::High);
280        assert_eq!(ov.priority, OverridePriority::High);
281        assert_eq!(ov.downcast_value::<bool>().copied(), Some(true));
282    }
283
284    #[test]
285    fn at_priority_promotes() {
286        let ov = OptionOverride::new(TypeId::of::<OptionA>(), 7i64);
287        let promoted = ov.at_priority(OverridePriority::High);
288        assert_eq!(promoted.priority, OverridePriority::High);
289    }
290}