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}