tiger_lib/
context.rs

1//! [`ScopeContext`] tracks our knowledge of the scope types used in script and validates its consistency.
2
3use std::borrow::Cow;
4use std::thread::panicking;
5
6use crate::everything::Everything;
7use crate::game::Game;
8use crate::helpers::{ActionOrEvent, TigerHashMap, stringify_choices};
9use crate::report::{ErrorKey, ReportBuilderFull, err, warn};
10use crate::scopes::Scopes;
11use crate::token::Token;
12
13/// When reporting an unknown scope, list alternative scope names if there are not more than this.
14const MAX_SCOPE_NAME_LIST: usize = 6;
15
16/// The `ScopeContext` represents what we know about the scopes leading to the `Block`
17/// currently being validated.
18#[derive(Clone, Debug)]
19pub struct ScopeContext {
20    /// `scope_stack` contains all known previous scopes, with `this` at the end. The oldest scope
21    /// is at index 0. INVARIANT: it is never empty, because there is always `this`.
22    ///
23    /// Normally, `this` starts as a `ScopeEntry::Rootref`, but there are cases where the
24    /// relationship to root is not known.
25    scope_stack: Vec<ScopeEntry>,
26
27    /// root is always a `ScopeEntry::Scope`
28    root: ScopeEntry,
29
30    /// `from` is the previous event root, and can be stacked like `from.from`.
31    /// The topmost `from` is at index 0, the next older one is at index 1, etc.
32    /// A `from` entry is always a `ScopeEntry::Scope`.
33    #[cfg(feature = "hoi4")]
34    from: Vec<ScopeEntry>,
35
36    /// Names of named scopes; the values are indices into the `named` vector.
37    scope_names: TigerHashMap<&'static str, (usize, Temporary)>,
38    /// Names of named lists; the values are indices into the `named` vector.
39    scope_list_names: TigerHashMap<&'static str, (usize, Temporary)>,
40    /// Names of local variables; the values are indices into the `named` vector.
41    local_names: TigerHashMap<&'static str, usize>,
42    /// Names of local variable lists; the values are indices into the `named` vector.
43    local_list_names: TigerHashMap<&'static str, usize>,
44
45    /// Named scope values are `ScopeEntry::Scope` or `ScopeEntry::Named` or `ScopeEntry::Rootref`.
46    /// Invariant: there are no cycles in the array via `ScopeEntry::Named` entries.
47    /// Invariant: entries are only added, never removed or rearranged.
48    /// This is because the indices are used by `ScopeEntry::Named` values throughout this `ScopeContext`.
49    named: Vec<ScopeEntry>,
50
51    /// Same indices as `named`, is a token iff the named scope is expected to be set on entry to the current scope context.
52    /// Invariant: `named` and `is_input` are the same length.
53    is_input: Vec<Option<Token>>,
54
55    /// Is this scope level a level in progress? `is_builder` is used when evaluating scope chains
56    /// like `root.liege.primary_title`. It affects the handling of `prev`, because the builder
57    /// scope is not a real scope level yet.
58    is_builder: bool,
59
60    /// Was this `ScopeContext` created as an unrooted context? Unrooted means we do not know
61    /// whether `this` and `root` are the same at the start.
62    is_unrooted: bool,
63
64    /// How many dummy `prev` levels were added to this scope context?
65    /// They affect how the scope context is cleaned up.
66    /// Usually 0 or 1, but imperator and hoi4 can have multiple prev levels.
67    prev_levels: usize,
68
69    /// Is this scope context one where all the named scopes are (or should be) known in advance?
70    /// If `strict_scopes` is false, then the `ScopeContext` will assume any name might be a valid
71    /// scope name that we just don't know about yet.
72    strict_scopes: bool,
73
74    /// A special flag for scope contexts that are known to be wrong. It's used for the
75    /// `scope_override` config file feature. If `no_warn` is set then this `ScopeContext` will not
76    /// emit any reports.
77    no_warn: bool,
78
79    /// A token indicating where this context was created and its named scopes were initialized.
80    source: Token,
81
82    /// A history of the actions and events that were triggered on the way from `source` to the
83    /// current context.
84    traceback: Vec<ActionOrEvent>,
85}
86
87#[derive(Clone, Debug, Default)]
88/// `ScopeEntry` is a description of what we know of a scope's type and its connection to other
89/// scopes.
90///
91/// It is used both to look up a scope's type, and to propagate knowledge about that type backward
92/// to the scope's source. For example if `this` is a Rootref, and we find out that `this` is a
93/// `Character`, then `root` must be a `Character` too.
94enum ScopeEntry {
95    /// Backref is for when the current scope is made with `prev` or `this`.
96    /// It counts as a scope in the chain, for purposes of `prev` and such, but any updates
97    /// to it (such as narrowing of scope types) need to be propagated back to the
98    /// real origin of that scope.
99    ///
100    /// The backref number is a relative count into `scope_stack`, counting backwards from the
101    /// entry in question.
102    /// It may go past the edge of `scope_stack`, if the script being analyzed uses `prev` farther
103    /// than we know about
104    ///
105    /// INVARIANT: The `usize` must not be zero.
106    Backref(usize),
107
108    /// Fromref is for when the current scope is made with `from`.
109    /// The fromref number is 0 for a single `from`, 1 for `from.from`, etc.
110    #[cfg(feature = "hoi4")]
111    Fromref(usize),
112
113    /// A Rootref is for when the current scope is made with `root`. Most of the time,
114    /// we also start with `this` being a Rootref.
115    #[default]
116    Rootref,
117
118    /// `Reason` why we think the `Scopes` value is what it is.
119    /// It's usually the token that was the cause of the latest narrowing.
120    Scope(Scopes, Reason),
121
122    /// The scope takes its value from a named scope. The `usize` is an index into the `ScopeContext::named` vector.
123    Named(usize),
124
125    /// The scope takes its value from a global variable
126    #[cfg(feature = "jomini")]
127    GlobalVar(&'static str, Reason),
128
129    /// The scope takes its value from a global variable list
130    #[cfg(feature = "jomini")]
131    GlobalList(&'static str, Reason),
132
133    /// The scope takes its value from a normal variable
134    #[cfg(feature = "jomini")]
135    Var(&'static str, Reason),
136
137    /// The scope takes its value from a variable list
138    #[cfg(feature = "jomini")]
139    VarList(&'static str, Reason),
140}
141
142/// This enum records the reason why we think a certain scope has the type it does.
143/// It is used for error reporting.
144///
145/// TODO: make a `ReasonRef` that contains an `&Token`, and a `Borrow` impl for it.
146/// This will avoid some cloning.
147#[derive(Clone, Debug)]
148pub enum Reason {
149    /// The reason can be explained by pointing at some token
150    Token(Token),
151    /// The scope type was deduced from a named scope's name; the `Token` points at that name in
152    /// the script.
153    Name(Token),
154    /// The scope was supplied by the game engine. The `Token` points at a key explaining this, for
155    /// example the key of an `Item` or the field key of a trigger or effect in an item.
156    Builtin(Token),
157    /// The vic3 engine evaluates `multiplier` in `add_modifier` in root scope, which is probably a
158    /// bug. Explain it to the user when it comes up. The `Token` points at the `multiplier` key.
159    #[cfg(feature = "vic3")]
160    MultiplierBug(Token),
161    /// The scope type was taken from info about a variable or variable list in the given namespace.
162    #[cfg(feature = "jomini")]
163    VariableReference(Token, &'static str),
164}
165
166/// Information about a temporarily suspended scope-building operation.
167/// This is used in constructs like `squared_distance(prev.capital_province)`,
168/// where the builder scope opened by `squared_distance` needs to be preserved
169/// while `prev.capital_province` is evaluated in the original scope.
170#[repr(transparent)]
171#[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
172pub struct StashedBuilder {
173    this: ScopeEntry,
174}
175
176/// The essential characteristics of a `ScopeContext` for the purpose of deciding whether an event
177/// has already been evaluated with a similar-enough `ScopeContext`.
178#[derive(Clone, Debug, PartialEq, Eq, Hash)]
179pub struct Signature {
180    root: Scopes,
181    // these are all sorted
182    scope_names: Vec<(&'static str, Scopes)>,
183    scope_list_names: Vec<(&'static str, Scopes)>,
184    local_names: Vec<(&'static str, Scopes)>,
185    local_list_names: Vec<(&'static str, Scopes)>,
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub enum Temporary {
190    No,
191    Yes,
192    Wiped,
193}
194
195/// Backref index to pass to refer to the `this` scope.
196const THIS: usize = 1;
197/// Backref index to pass to refer to the `prev` scope.
198const PREV: usize = 2;
199
200impl Reason {
201    pub fn token(&self) -> &Token {
202        match self {
203            Reason::Token(t) | Reason::Name(t) | Reason::Builtin(t) => t,
204            #[cfg(feature = "vic3")]
205            Reason::MultiplierBug(t) => t,
206            #[cfg(feature = "jomini")]
207            Reason::VariableReference(t, _) => t,
208        }
209    }
210
211    // TODO: change this to Display ?
212    pub fn msg(&self) -> Cow<'_, str> {
213        match self {
214            Reason::Token(t) => Cow::Owned(format!("deduced from `{t}` here")),
215            Reason::Name(_) => Cow::Borrowed("deduced from the scope's name"),
216            Reason::Builtin(_) => Cow::Borrowed("supplied by the game engine"),
217            #[cfg(feature = "vic3")]
218            Reason::MultiplierBug(_) => {
219                Cow::Borrowed("evaluated in root scope for `multiplier` (as of 1.9.8")
220            }
221            #[cfg(feature = "jomini")]
222            Reason::VariableReference(t, namespace) => {
223                Cow::Owned(format!("based on {namespace}{t}"))
224            }
225        }
226    }
227}
228
229impl ScopeEntry {
230    fn deduce<T: Into<Token>>(token: T) -> ScopeEntry {
231        let token = token.into();
232        if let Some(scopes) = scope_type_from_name(token.as_str()) {
233            ScopeEntry::Scope(scopes, Reason::Name(token))
234        } else {
235            ScopeEntry::Scope(Scopes::all(), Reason::Token(token))
236        }
237    }
238}
239
240impl ScopeContext {
241    /// Make a new `ScopeContext`, with `this` and `root` the same, and `root` of the given scope
242    /// types. `token` is used when reporting errors about the use of `root`.
243    pub fn new<T: Into<Token>>(root: Scopes, token: T) -> Self {
244        let token = token.into();
245        ScopeContext {
246            scope_stack: vec![ScopeEntry::Rootref],
247            root: ScopeEntry::Scope(root, Reason::Builtin(token.clone())),
248            #[cfg(feature = "hoi4")]
249            from: Vec::new(),
250            scope_names: TigerHashMap::default(),
251            scope_list_names: TigerHashMap::default(),
252            local_names: TigerHashMap::default(),
253            local_list_names: TigerHashMap::default(),
254            named: Vec::new(),
255            is_input: Vec::new(),
256            is_builder: false,
257            is_unrooted: false,
258            prev_levels: 0,
259            strict_scopes: true,
260            no_warn: false,
261            source: token,
262            traceback: Vec::new(),
263        }
264    }
265
266    /// Make a new `ScopeContext`, with `this` and `root` unconnected, and `this` of the given scope
267    /// types. `token` is used when reporting errors about the use of `this`, `root`, or `prev`.
268    ///
269    /// This function is useful for the scope contexts created for scripted effects, scripted
270    /// triggers, and script values. In those, it's not known what the caller's `root` is.
271    pub fn new_unrooted<T: Into<Token>>(this: Scopes, token: T) -> Self {
272        let token = token.into();
273        ScopeContext {
274            scope_stack: vec![ScopeEntry::Scope(this, Reason::Token(token.clone()))],
275            root: ScopeEntry::Scope(Scopes::all(), Reason::Token(token.clone())),
276            #[cfg(feature = "hoi4")]
277            from: Vec::new(),
278            scope_names: TigerHashMap::default(),
279            scope_list_names: TigerHashMap::default(),
280            local_names: TigerHashMap::default(),
281            local_list_names: TigerHashMap::default(),
282            named: Vec::new(),
283            is_input: Vec::new(),
284            is_builder: false,
285            is_unrooted: true,
286            prev_levels: 0,
287            strict_scopes: true,
288            no_warn: false,
289            source: token,
290            traceback: Vec::new(),
291        }
292    }
293
294    /// Make a new `ScopeContext`, with `this` and `root` unconnected
295    /// and of separate scope types.
296    /// `token` is used when reporting errors about the use of `this` or `root`.
297    ///
298    /// This function is useful in specialized contexts where the game engine
299    /// provides different `this` and `root`.
300    #[cfg(feature = "hoi4")]
301    pub fn new_separate_root<T: Into<Token>>(root: Scopes, this: Scopes, token: T) -> Self {
302        let token = token.into();
303        let mut sc = ScopeContext::new(root, token.clone());
304        *sc.scope_stack.last_mut().unwrap() = ScopeEntry::Scope(this, Reason::Builtin(token));
305        sc
306    }
307
308    #[cfg(feature = "hoi4")]
309    pub fn new_with_prev<T: Into<Token>>(root: Scopes, prev: Scopes, token: T) -> Self {
310        let token = token.into();
311        let mut sc = ScopeContext::new(root, token.clone());
312        sc.scope_stack
313            .insert(sc.scope_stack.len() - 1, ScopeEntry::Scope(prev, Reason::Token(token)));
314        sc.prev_levels += 1;
315        sc
316    }
317
318    /// Declare whether all the named scopes in this scope context are known. Default is true.
319    ///
320    /// Set this to false in for example events, which start with the scopes defined by their
321    /// triggering context.
322    ///
323    /// Having strict scopes set to true makes the `ScopeContext` emit errors when encountering
324    /// unknown scope names.
325    pub fn set_strict_scopes(&mut self, strict: bool) {
326        self.strict_scopes = strict;
327    }
328
329    /// Return whether this `ScopeContext` has strict scopes set to true.
330    /// See [`Self::set_strict_scopes`].
331    pub fn is_strict(&self) -> bool {
332        self.strict_scopes
333    }
334
335    /// Set whether this `ScopeContext` should emit reports at all. `no_warn` defaults to false.
336    ///
337    /// It's used for scope contexts that are known to be wrong, related to the `scope_override` config file feature.
338    pub fn set_no_warn(&mut self, no_warn: bool) {
339        self.no_warn = no_warn;
340    }
341
342    /// Change this context's `source` value to something more appropriate than the default (which
343    /// is the token passed to `new`).
344    pub fn set_source<T: Into<Token>>(&mut self, source: T) {
345        self.source = source.into();
346    }
347
348    /// Helper function for `root_for_event` and `root_for_action`.
349    fn root_for(&self, trace: ActionOrEvent, data: &Everything) -> Option<Self> {
350        if !self.strict_scopes || self.no_warn || self.traceback.contains(&trace) {
351            return None;
352        }
353        let mut new_sc = self.clone();
354        for named in &mut new_sc.named {
355            if matches!(named, ScopeEntry::Rootref) {
356                *named = new_sc.root.clone();
357            }
358        }
359        #[cfg(feature = "hoi4")]
360        new_sc.from.insert(0, new_sc.root.clone());
361        let (scopes, reason) = new_sc.scopes_reason(data);
362        new_sc.root = ScopeEntry::Scope(scopes, reason.clone());
363        new_sc.scope_stack = vec![ScopeEntry::Rootref];
364        new_sc.prev_levels = 0;
365        new_sc.is_unrooted = false;
366        new_sc.traceback.push(trace);
367        new_sc.wipe_temporaries();
368        Some(new_sc)
369    }
370
371    /// Create a `ScopeContext` to use for a triggered event, if validating the event with this
372    /// scope context is useful.
373    pub fn root_for_event<T: Into<Token>>(&self, event_id: T, data: &Everything) -> Option<Self> {
374        self.root_for(ActionOrEvent::new_event(event_id.into()), data)
375    }
376
377    /// Create a `ScopeContext` to use for a triggered action, if validating the action with this
378    /// scope context is useful.
379    pub fn root_for_action<T: Into<Token>>(&self, action: T, data: &Everything) -> Option<Self> {
380        let action = action.into();
381        if self.source == action {
382            return None;
383        }
384        self.root_for(ActionOrEvent::new_action(action), data)
385    }
386
387    pub fn wipe_temporaries(&mut self) {
388        for (_, t) in self.scope_names.values_mut() {
389            if *t == Temporary::Yes {
390                *t = Temporary::Wiped;
391            }
392        }
393        for (_, t) in self.scope_list_names.values_mut() {
394            if *t == Temporary::Yes {
395                *t = Temporary::Wiped;
396            }
397        }
398    }
399
400    /// Change the scope type and related token of `root` for this `ScopeContext`.
401    ///
402    /// This function is mainly used in the setup of a `ScopeContext` before using it.
403    /// It's a bit of a hack and shouldn't be used.
404    /// TODO: get rid of this.
405    #[cfg(feature = "ck3")] // happens not to be used by vic3
406    pub fn change_root<T: Into<Token>>(&mut self, root: Scopes, token: T) {
407        self.root = ScopeEntry::Scope(root, Reason::Builtin(token.into()));
408    }
409
410    #[doc(hidden)]
411    fn define_name_internal(
412        &mut self,
413        name: &'static str,
414        scopes: Scopes,
415        reason: Reason,
416        temp: Temporary,
417    ) {
418        if let Some((idx, t)) = self.scope_names.get_mut(name) {
419            *t = temp;
420            let idx = *idx;
421            Self::break_chains_to(&mut self.named, idx);
422            self.named[idx] = ScopeEntry::Scope(scopes, reason);
423        } else {
424            self.scope_names.insert(name, (self.named.len(), temp));
425            self.named.push(ScopeEntry::Scope(scopes, reason));
426            self.is_input.push(None);
427        }
428    }
429
430    /// Declare that this `ScopeContext` contains a named scope of the given name and type,
431    /// supplied by the game engine.
432    ///
433    /// The associated `token` will be used in error reports related to this named scope.
434    pub fn define_name<T: Into<Token>>(&mut self, name: &'static str, scopes: Scopes, token: T) {
435        self.define_name_internal(name, scopes, Reason::Builtin(token.into()), Temporary::No);
436    }
437
438    /// Declare that this `ScopeContext` contains a named scope of the given name and type,
439    /// *not* supplied by the game engine but deduced from script.
440    ///
441    /// The associated `token` will be used in error reports related to this named scope.
442    /// The token should reflect why we think the named scope has the scope type it has.
443    pub fn define_name_token<T: Into<Token>>(
444        &mut self,
445        name: &'static str,
446        scopes: Scopes,
447        token: T,
448        temp: Temporary,
449    ) {
450        self.define_name_internal(name, scopes, Reason::Token(token.into()), temp);
451    }
452
453    /// Look up a named scope and return its scope types if it's known.
454    ///
455    /// Callers should probably check [`Self::is_strict()`] as well.
456    pub fn is_name_defined(&mut self, name: &str, data: &Everything) -> Option<Scopes> {
457        if let Some(&(idx, temp)) = self.scope_names.get(name) {
458            if temp == Temporary::Wiped {
459                None
460            } else {
461                #[allow(clippy::indexing_slicing)] // invariant guarantees no panic
462                Some(match self.named[idx] {
463                    ScopeEntry::Scope(s, _) => s,
464                    ScopeEntry::Backref(_) => unreachable!(),
465                    #[cfg(feature = "hoi4")]
466                    ScopeEntry::Fromref(_) => unreachable!(),
467                    ScopeEntry::Rootref => self.resolve_root().0,
468                    ScopeEntry::Named(idx) => self.resolve_named(idx, data).0,
469                    #[cfg(feature = "jomini")]
470                    ScopeEntry::GlobalVar(name, _) => data.global_scopes.scopes(name),
471                    #[cfg(feature = "jomini")]
472                    ScopeEntry::GlobalList(name, _) => data.global_list_scopes.scopes(name),
473                    #[cfg(feature = "jomini")]
474                    ScopeEntry::Var(name, _) => data.variable_scopes.scopes(name),
475                    #[cfg(feature = "jomini")]
476                    ScopeEntry::VarList(name, _) => data.variable_list_scopes.scopes(name),
477                })
478            }
479        } else {
480            None
481        }
482    }
483
484    /// Put a scope entry on the FROM chain. It becomes the new FROM, and the old one (if any)
485    /// becomes FROM.FROM, etc.
486    #[cfg(feature = "hoi4")]
487    pub fn push_as_from<T: Into<Token>>(&mut self, scopes: Scopes, token: T) {
488        self.from.insert(0, ScopeEntry::Scope(scopes, Reason::Builtin(token.into())));
489    }
490
491    /// This is called when the script does `exists = scope:name`.
492    ///
493    /// It records `name` as "known", but with no scope type information, and records that the
494    /// caller is expected to provide this scope.
495    ///
496    /// The `ScopeContext` is not smart enough to track optionally existing scopes. It assumes
497    /// that if you do `exists` on a scope, then from that point on it exists. Improving this would
498    /// be a big project.
499    pub fn exists_scope<T: Into<Token>>(&mut self, name: &'static str, token: T) {
500        if !self.scope_names.contains_key(name) {
501            let idx = self.named.len();
502            self.scope_names.insert(name, (idx, Temporary::No));
503            self.named.push(ScopeEntry::deduce(token));
504            self.is_input.push(None);
505        }
506    }
507
508    /// This is called when the script does `exists = local_var:name` or `has_local_variable = name`.
509    ///
510    /// It records `name` as "known", but with no scope type information, and records that the
511    /// caller is expected to provide this local variable.
512    pub fn exists_local<T: Into<Token>>(&mut self, name: &'static str, token: T) {
513        if !self.local_names.contains_key(name) {
514            self.local_names.insert(name, self.named.len());
515            self.named.push(ScopeEntry::deduce(token));
516            self.is_input.push(None);
517        }
518    }
519
520    /// This is called when the script does `has_local_variable_list = name`.
521    ///
522    /// It records `name` as "known", but with no scope type information, and records that the
523    /// caller is expected to provide this local variable list.
524    pub fn exists_local_list<T: Into<Token>>(&mut self, name: &'static str, token: T) {
525        if !self.local_list_names.contains_key(name) {
526            self.local_list_names.insert(name, self.named.len());
527            self.named.push(ScopeEntry::deduce(token));
528            self.is_input.push(None);
529        }
530    }
531
532    #[doc(hidden)]
533    fn define_list_internal(
534        &mut self,
535        name: &'static str,
536        scopes: Scopes,
537        reason: Reason,
538        temp: Temporary,
539    ) {
540        if let Some(&(idx, _)) = self.scope_list_names.get(name) {
541            Self::break_chains_to(&mut self.named, idx);
542            self.named[idx] = ScopeEntry::Scope(scopes, reason);
543        } else {
544            self.scope_list_names.insert(name, (self.named.len(), temp));
545            self.named.push(ScopeEntry::Scope(scopes, reason));
546            self.is_input.push(None);
547        }
548    }
549
550    /// Declare that this `ScopeContext` contains a list of the given name and type,
551    /// supplied by the game engine.
552    ///
553    /// The associated `token` will be used in error reports related to this list.
554    ///
555    /// Lists and named scopes exist in different namespaces, but under the hood
556    /// `ScopeContext` treats them the same. This means that lists are expected to
557    /// contain items of a single scope type, which sometimes leads to false positives.
558    pub fn define_list<T: Into<Token>>(&mut self, name: &'static str, scopes: Scopes, token: T) {
559        self.define_list_internal(name, scopes, Reason::Builtin(token.into()), Temporary::No);
560    }
561
562    /// This is like [`Self::define_name()`], but `scope:name` is declared equal to the current `this`.
563    pub fn save_current_scope(&mut self, name: &'static str, temp: Temporary) {
564        if let Some((idx, t)) = self.scope_names.get_mut(name) {
565            *t = temp;
566            let idx = *idx;
567            Self::break_chains_to(&mut self.named, idx);
568            let entry = self.resolve_backrefs(THIS);
569            // Guard against `scope:foo = { save_scope_as = foo }`
570            if let ScopeEntry::Named(i) = entry {
571                if *i == idx {
572                    // Leave the scope as its original value
573                    return;
574                }
575            }
576            self.named[idx] = entry.clone();
577        } else {
578            self.scope_names.insert(name, (self.named.len(), temp));
579            self.named.push(self.resolve_backrefs(THIS).clone());
580            self.is_input.push(None);
581        }
582    }
583
584    /// Sets a local variable to the provided scope type
585    #[cfg(feature = "jomini")]
586    pub fn set_local_variable(&mut self, name: &Token, scope: Scopes) {
587        if let Some(&idx) = self.local_names.get(name.as_str()) {
588            Self::break_chains_to(&mut self.named, idx);
589            self.named[idx] = ScopeEntry::Scope(scope, Reason::Token(name.clone()));
590        } else {
591            self.local_names.insert(name.as_str(), self.named.len());
592            self.named.push(ScopeEntry::Scope(scope, Reason::Token(name.clone())));
593            self.is_input.push(None);
594        }
595    }
596
597    /// If list `name` exists, narrow its scope type down to `this` and narrow the `this` scope
598    /// types down to the existing list.
599    /// Otherwise, define it as having the same scope type as `this`.
600    pub fn define_or_expect_list_this(&mut self, name: &Token, data: &Everything, temp: Temporary) {
601        if let Some((idx, t)) = self.scope_list_names.get_mut(name.as_str()) {
602            *t = temp;
603            let idx = *idx;
604            // TODO: remove need to clone reason
605            let (s, reason) = self.resolve_named(idx, data);
606            self.expect(s, &reason.clone(), data);
607            let (s, reason) = self.scopes_reason(data);
608            let reason = reason.clone();
609            self.expect_named(idx, s, &reason, data);
610            // It often happens that an iterator does is_in_list before add_to_list,
611            // and in those cases we want the add_to_list to take precedence: conclude that the
612            // list is being built here, and isn't an input list.
613            self.is_input[idx] = None;
614        } else {
615            self.scope_list_names.insert(name.as_str(), (self.named.len(), temp));
616            self.named.push(self.resolve_backrefs(THIS).clone());
617            self.is_input.push(None);
618        }
619    }
620
621    /// If list `name` exists, narrow its scope type down to the given scopes.
622    /// Otherwise, define it as having the given scopes.
623    #[allow(dead_code)]
624    pub fn define_or_expect_list(
625        &mut self,
626        name: &Token,
627        scope: Scopes,
628        data: &Everything,
629        temp: Temporary,
630    ) {
631        if let Some((idx, t)) = self.scope_list_names.get_mut(name.as_str()) {
632            *t = temp;
633            let idx = *idx;
634            self.expect_named(idx, scope, &Reason::Token(name.clone()), data);
635            // It often happens that an iterator does is_in_list before add_to_list,
636            // and in those cases we want the add_to_list to take precedence: conclude that the
637            // list is being built here, and isn't an input list.
638            self.is_input[idx] = None;
639        } else {
640            self.scope_list_names.insert(name.as_str(), (self.named.len(), temp));
641            self.named.push(ScopeEntry::Scope(scope, Reason::Token(name.clone())));
642            self.is_input.push(None);
643        }
644    }
645
646    /// If local variable list `name` exists, narrow its scope type down to the given scopes.
647    /// Otherwise, define it as having the given scopes.
648    pub fn define_or_expect_local_list(&mut self, name: &Token, scope: Scopes, data: &Everything) {
649        if let Some(&idx) = self.local_list_names.get(name.as_str()) {
650            self.expect_named(idx, scope, &Reason::Token(name.clone()), data);
651            // It often happens that an iterator does is_in_list before add_to_list,
652            // and in those cases we want the add_to_list to take precedence: conclude that the
653            // list is being built here, and isn't an input list.
654            self.is_input[idx] = None;
655        } else {
656            self.local_list_names.insert(name.as_str(), self.named.len());
657            self.named.push(ScopeEntry::Scope(scope, Reason::Token(name.clone())));
658            self.is_input.push(None);
659        }
660    }
661
662    /// Expect list `name` to be known and (with strict scopes) warn if it isn't.
663    /// Narrow the type of `this` down to the list's type.
664    pub fn expect_list(&mut self, name: &Token, data: &Everything) {
665        if let Some((idx, t)) = self.scope_list_names.get(name.as_str()) {
666            if *t == Temporary::Wiped {
667                let msg = format!("list `{name}` was temporary and is no longer available here");
668                err(ErrorKey::TemporaryScope).weak().msg(msg).loc(name).push();
669            } else {
670                let (s, reason) = self.resolve_named(*idx, data);
671                let reason = reason.clone(); // TODO: remove need to clone
672                self.expect3(s, &reason, name, THIS, "list", data);
673            }
674        } else if self.strict_scopes {
675            let msg = "unknown list";
676            err(ErrorKey::UnknownList).weak().msg(msg).loc(name).push();
677        }
678    }
679
680    /// Expect local variable list `name` to be known and (with strict scopes) warn if it isn't.
681    /// Narrow the type of `this` down to the list's type.
682    #[cfg(feature = "jomini")]
683    pub fn expect_local_list(&mut self, name: &Token, data: &Everything) {
684        if let Some(&idx) = self.local_list_names.get(name.as_str()) {
685            let (s, reason) = self.resolve_named(idx, data);
686            let reason = reason.clone(); // TODO: remove need to clone
687            self.expect3(s, &reason, name, THIS, "local variable list", data);
688        } else if self.strict_scopes {
689            let msg = "unknown local variable list";
690            err(ErrorKey::UnknownList).weak().msg(msg).loc(name).push();
691        }
692    }
693
694    /// Expect local variable `name` to be known and (with strict scopes) warn if it isn't.
695    #[cfg(feature = "jomini")]
696    pub fn expect_local(&mut self, name: &Token, scope: Scopes, data: &Everything) {
697        if let Some(&idx) = self.local_names.get(name.as_str()) {
698            self.expect_named(idx, scope, &Reason::Token(name.clone()), data);
699        } else if self.strict_scopes {
700            let msg = "unknown local variable";
701            err(ErrorKey::UnknownVariable).msg(msg).loc(name).push();
702        }
703    }
704
705    /// Cut `idx` out of any [`ScopeEntry::Named`] chains. This avoids infinite loops.
706    #[doc(hidden)]
707    fn break_chains_to(named: &mut [ScopeEntry], idx: usize) {
708        for i in 0..named.len() {
709            if i == idx {
710                continue;
711            }
712            if let ScopeEntry::Named(ni) = named[i] {
713                if ni == idx {
714                    named[i] = named[idx].clone();
715                }
716            }
717        }
718    }
719
720    /// Open a new scope level of `scopes` scope type. Record `token` as the reason for this type.
721    ///
722    /// This is mostly used by iterators.
723    /// `prev` will refer to the previous scope level.
724    pub fn open_scope(&mut self, scopes: Scopes, token: Token) {
725        self.scope_stack.push(ScopeEntry::Scope(scopes, Reason::Token(token)));
726    }
727
728    /// Open a new, temporary scope level. Initially it will have its `this` the same as the
729    /// previous level's `this`.
730    ///
731    /// The purpose is to handle scope chains like `root.liege.primary_title`. Call the `replace_`
732    /// functions to update the value of `this`, and at the end either confirm the new scope level
733    /// with [`Self::finalize_builder()`] or discard it with [`Self::close()`].
734    pub fn open_builder(&mut self) {
735        self.scope_stack.push(ScopeEntry::Backref(THIS));
736        self.is_builder = true;
737    }
738
739    #[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
740    pub fn stash_builder(&mut self) -> StashedBuilder {
741        let stash = StashedBuilder { this: self.scope_stack.pop().unwrap() };
742        self.is_builder = false;
743        stash
744    }
745
746    #[cfg(any(feature = "ck3", feature = "vic3", feature = "eu5"))]
747    pub fn unstash_builder(&mut self, stash: StashedBuilder) {
748        self.scope_stack.push(stash.this);
749        self.is_builder = true;
750    }
751
752    /// Declare that the temporary scope level opened with [`Self::open_builder()`] is a real scope level.
753    pub fn finalize_builder(&mut self) {
754        self.is_builder = false;
755    }
756
757    /// Exit a scope level and return to the previous level.
758    pub fn close(&mut self) {
759        self.scope_stack.pop().expect("matching open and close scopes");
760        self.is_builder = false;
761    }
762
763    /// Return an object that captures the essentials of this `ScopeContext`, to be used for
764    /// hashing.
765    pub fn signature(&self, data: &Everything) -> Signature {
766        fn process_scope_names(
767            sc: &ScopeContext,
768            names: &TigerHashMap<&'static str, (usize, Temporary)>,
769            data: &Everything,
770        ) -> Vec<(&'static str, Scopes)> {
771            let mut names: Vec<_> = names
772                .iter()
773                .filter(|(_, (_, temp))| *temp != Temporary::Wiped)
774                .map(|(&name, (i, _))| (name, sc.resolve_named(*i, data).0))
775                .collect();
776            names.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
777            names
778        }
779
780        fn process_local_names(
781            sc: &ScopeContext,
782            names: &TigerHashMap<&'static str, usize>,
783            data: &Everything,
784        ) -> Vec<(&'static str, Scopes)> {
785            let mut names: Vec<_> =
786                names.iter().map(|(&name, &i)| (name, sc.resolve_named(i, data).0)).collect();
787            names.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
788            names
789        }
790
791        let root = self.resolve_root().0;
792
793        Signature {
794            root,
795            scope_names: process_scope_names(self, &self.scope_names, data),
796            scope_list_names: process_scope_names(self, &self.scope_list_names, data),
797            local_names: process_local_names(self, &self.local_names, data),
798            local_list_names: process_local_names(self, &self.local_list_names, data),
799        }
800    }
801
802    /// Replace the `this` in a temporary scope level with the given `scopes` type and record
803    /// `token` as the reason for this type.
804    ///
805    /// This is used when a scope chain starts with something absolute like `faith:catholic`.
806    pub fn replace(&mut self, scopes: Scopes, token: Token) {
807        *self.scope_stack.last_mut().unwrap() = ScopeEntry::Scope(scopes, Reason::Token(token));
808    }
809
810    /// Replace the `this` in a temporary scope level with a reference to `root`.
811    pub fn replace_root(&mut self) {
812        *self.scope_stack.last_mut().unwrap() = ScopeEntry::Rootref;
813    }
814
815    /// Replace the `this` in a temporary scope level with a reference to the previous scope level.
816    pub fn replace_prev(&mut self) {
817        let this = self.scope_stack.last_mut().unwrap();
818        let backref = if Game::is_imperator() || Game::is_hoi4() {
819            // Allow `prev.prev` etc
820            match this {
821                ScopeEntry::Backref(r) => *r + 1,
822                _ => PREV,
823            }
824        } else {
825            PREV
826        };
827        *this = ScopeEntry::Backref(backref);
828        while 1 + backref > self.scope_stack.len() {
829            // We went further back up the scope chain than we know about.
830            let entry = if self.is_unrooted {
831                ScopeEntry::Scope(Scopes::all(), Reason::Token(self.source.clone()))
832            } else {
833                ScopeEntry::Scope(Scopes::None, Reason::Builtin(self.source.clone()))
834            };
835            self.scope_stack.insert(0, entry);
836            self.prev_levels += 1;
837        }
838    }
839
840    /// Replace the `this` in a temporary scope level with a reference to its previous event root.
841    #[cfg(feature = "hoi4")]
842    pub fn replace_from(&mut self) {
843        let this = self.scope_stack.last_mut().unwrap();
844        match this {
845            ScopeEntry::Fromref(r) => *this = ScopeEntry::Fromref(*r + 1),
846            _ => *this = ScopeEntry::Fromref(0),
847        }
848    }
849
850    /// Replace the `this` in a temporary scope level with a reference to the real level below it.
851    ///
852    /// This is usually a no-op, used when scope chains start with `this`. If a scope chain has
853    /// `this` in the middle of the chain (which itself will trigger a warning) then it resets the
854    /// temporary scope level to the way it started.
855    pub fn replace_this(&mut self) {
856        *self.scope_stack.last_mut().unwrap() = ScopeEntry::Backref(THIS);
857    }
858
859    /// Replace the `this` in a temporary scope level with a reference to the named scope `name`.
860    ///
861    /// This is used when a scope chain starts with `scope:name`. The `token` is expected to be the
862    /// `scope:name` token.
863    pub fn replace_named_scope(&mut self, name: &'static str, token: &Token) {
864        *self.scope_stack.last_mut().unwrap() = ScopeEntry::Named(self.named_index(name, token));
865    }
866
867    /// Replace the `this` in a temporary scope level with a reference to the local variable `name`.
868    ///
869    /// This is used when a scope chain starts with `local_var:name`. The `token` is expected to be the
870    /// `local_var:name` token.
871    pub fn replace_local_variable(&mut self, name: &'static str, token: &Token) {
872        *self.scope_stack.last_mut().unwrap() = ScopeEntry::Named(self.local_index(name, token));
873    }
874
875    /// Replace the `this` in a temporary scope level with a reference to the global variable `name`.
876    ///
877    /// This is used when a scope chain starts with `global_var:name`. The `token` is expected to be the
878    /// `global_var:name` token.
879    #[cfg(feature = "jomini")]
880    pub fn replace_global_variable(&mut self, name: &'static str, token: &Token) {
881        *self.scope_stack.last_mut().unwrap() =
882            ScopeEntry::GlobalVar(name, Reason::VariableReference(token.clone(), "global_var:"));
883    }
884
885    /// Replace the `this` in a temporary scope level with a reference to the variable `name`.
886    ///
887    /// This is used when a scope chain starts with `var:name`. The `token` is expected to be the
888    /// `var:name` token.
889    #[cfg(feature = "jomini")]
890    pub fn replace_variable(&mut self, name: &'static str, token: &Token) {
891        *self.scope_stack.last_mut().unwrap() =
892            ScopeEntry::Var(name, Reason::VariableReference(token.clone(), "var:"));
893    }
894
895    /// Replace the `this` in a temporary scope level with a reference to the scope type of the
896    /// list `name`.
897    #[cfg(feature = "jomini")]
898    pub fn replace_list_entry(&mut self, name: &Token) {
899        *self.scope_stack.last_mut().unwrap() =
900            ScopeEntry::Named(self.named_list_index(name.as_str(), name));
901    }
902
903    /// Replace the `this` in a temporary scope level with a reference to the scope type of the
904    /// local variable list `name`.
905    #[cfg(feature = "jomini")]
906    pub fn replace_local_list_entry(&mut self, name: &Token) {
907        *self.scope_stack.last_mut().unwrap() =
908            ScopeEntry::Named(self.local_list_index(name.as_str(), name));
909    }
910
911    /// Replace the `this` in a temporary scope level with a reference to the scope type of the
912    /// global variable list `name`.
913    #[cfg(feature = "jomini")]
914    pub fn replace_global_list_entry(&mut self, name: &Token) {
915        *self.scope_stack.last_mut().unwrap() = ScopeEntry::GlobalList(
916            name.as_str(),
917            Reason::VariableReference(name.clone(), "global list "),
918        );
919    }
920
921    /// Replace the `this` in a temporary scope level with a reference to the scope type of the
922    /// variable list `name`.
923    #[cfg(feature = "jomini")]
924    pub fn replace_variable_list_entry(&mut self, name: &Token) {
925        *self.scope_stack.last_mut().unwrap() = ScopeEntry::VarList(
926            name.as_str(),
927            Reason::VariableReference(name.clone(), "variable list "),
928        );
929    }
930
931    /// Get the internal index of named scope `name`, either its existing index or a newly created one.
932    ///
933    /// If a new index has to be created, and `strict_scopes` is on, then a warning will be emitted.
934    #[doc(hidden)]
935    fn named_index(&mut self, name: &'static str, token: &Token) -> usize {
936        if let Some(&(idx, t)) = self.scope_names.get(name) {
937            if t == Temporary::Wiped {
938                let msg = format!("`scope:{name}` was temporary and is no longer available here");
939                self.log_traceback(err(ErrorKey::TemporaryScope).weak().msg(msg).loc(token)).push();
940            }
941            idx
942        } else {
943            let idx = self.named.len();
944            self.named.push(ScopeEntry::deduce(token));
945            if self.strict_scopes {
946                if !self.no_warn {
947                    let msg = format!("scope:{name} might not be available here");
948                    let mut builder = err(ErrorKey::StrictScopes).weak().msg(msg);
949                    if self.scope_names.len() <= MAX_SCOPE_NAME_LIST && !self.scope_names.is_empty()
950                    {
951                        let mut names: Vec<_> = self.scope_names.keys().copied().collect();
952                        names.sort_unstable();
953                        let info = format!("available names are {}", stringify_choices(&names));
954                        builder = builder.info(info);
955                    }
956                    self.log_traceback(builder.loc(token)).push();
957                }
958                // Don't treat it as an input scope, because we already warned about it
959                self.is_input.push(None);
960            } else {
961                self.is_input.push(Some(token.clone()));
962            }
963            // do this after the warnings above, so that it's not listed as available
964            self.scope_names.insert(name, (idx, Temporary::No));
965            idx
966        }
967    }
968
969    /// Get the internal index of local variable `name`, either its existing index or a newly created one.
970    ///
971    /// If a new index has to be created, and `strict_scopes` is on, then a warning will be emitted.
972    #[doc(hidden)]
973    fn local_index(&mut self, name: &'static str, token: &Token) -> usize {
974        if let Some(&idx) = self.local_names.get(name) {
975            idx
976        } else {
977            let idx = self.named.len();
978            self.named.push(ScopeEntry::deduce(token));
979            if self.strict_scopes {
980                if !self.no_warn {
981                    let msg = format!("local_var:{name} might not be available here");
982                    let mut builder = err(ErrorKey::StrictScopes).weak().msg(msg);
983                    if self.local_names.len() <= MAX_SCOPE_NAME_LIST && !self.local_names.is_empty()
984                    {
985                        let mut names: Vec<_> = self.local_names.keys().copied().collect();
986                        names.sort_unstable();
987                        let info = format!("available names are {}", stringify_choices(&names));
988                        builder = builder.info(info);
989                    }
990                    self.log_traceback(builder.loc(token)).push();
991                }
992                // Don't treat it as an input scope, because we already warned about it
993                self.is_input.push(None);
994            } else {
995                self.is_input.push(Some(token.clone()));
996            }
997            // do this after the warnings above, so that it's not listed as available
998            self.local_names.insert(name, idx);
999            idx
1000        }
1001    }
1002
1003    /// Same as [`Self::named_index()`], but for lists. No warning is emitted if a new list is created.
1004    /// Do warn if a temporary list was used after it was wiped.
1005    #[doc(hidden)]
1006    #[cfg(feature = "jomini")]
1007    fn named_list_index(&mut self, name: &'static str, token: &Token) -> usize {
1008        if let Some(&(idx, t)) = self.scope_list_names.get(name) {
1009            if t == Temporary::Wiped {
1010                let msg = format!("list `{name}` was temporary and is no longer available here");
1011                self.log_traceback(err(ErrorKey::TemporaryScope).weak().msg(msg).loc(token)).push();
1012            }
1013            idx
1014        } else {
1015            let idx = self.named.len();
1016            self.scope_list_names.insert(name, (idx, Temporary::No));
1017            self.named.push(ScopeEntry::Scope(Scopes::all(), Reason::Token(token.clone())));
1018            self.is_input.push(Some(token.clone()));
1019            idx
1020        }
1021    }
1022
1023    /// Same as [`Self::named_index()`], but for local variable lists.
1024    /// No warning is emitted if a new list is created.
1025    #[doc(hidden)]
1026    #[cfg(feature = "jomini")]
1027    fn local_list_index(&mut self, name: &'static str, token: &Token) -> usize {
1028        if let Some(&idx) = self.local_list_names.get(name) {
1029            idx
1030        } else {
1031            let idx = self.named.len();
1032            self.local_list_names.insert(name, idx);
1033            self.named.push(ScopeEntry::Scope(Scopes::all(), Reason::Token(token.clone())));
1034            self.is_input.push(Some(token.clone()));
1035            idx
1036        }
1037    }
1038
1039    /// Return true iff it's possible that `this` is the same type as one of the `scopes` types.
1040    pub fn can_be(&self, scopes: Scopes, data: &Everything) -> bool {
1041        self.scopes(data).intersects(scopes)
1042    }
1043
1044    /// Return the possible scope types of this scope level.
1045    // TODO: maybe specialize this function for performance?
1046    pub fn scopes(&self, data: &Everything) -> Scopes {
1047        self.scopes_reason(data).0
1048    }
1049
1050    /// Return the possible scope types of this local variable.
1051    #[cfg(feature = "jomini")]
1052    pub fn local_variable_scopes(&self, name: &str, data: &Everything) -> Scopes {
1053        if let Some(idx) = self.local_names.get(name).copied() {
1054            self.resolve_named(idx, data).0
1055        } else {
1056            Scopes::all_but_none()
1057        }
1058    }
1059
1060    /// Return the possible scope types of this local variable list.
1061    pub fn local_list_scopes(&self, name: &str, data: &Everything) -> Scopes {
1062        if let Some(idx) = self.local_list_names.get(name).copied() {
1063            self.resolve_named(idx, data).0
1064        } else {
1065            Scopes::all_but_none()
1066        }
1067    }
1068
1069    #[cfg(feature = "vic3")]
1070    pub fn get_multiplier_context(&self, key: &Token) -> Self {
1071        let scopes = self.resolve_root().0;
1072        let mut sc = ScopeContext::new(scopes, key);
1073        sc.root = ScopeEntry::Scope(scopes, Reason::MultiplierBug(key.clone()));
1074        sc
1075    }
1076
1077    /// Return the possible scope types of `root`, and the reason why we think it has those types
1078    fn resolve_root(&self) -> (Scopes, &Reason) {
1079        match self.root {
1080            ScopeEntry::Scope(s, ref reason) => (s, reason),
1081            _ => unreachable!(),
1082        }
1083    }
1084
1085    /// Return the possible scope types of a named scope or list, and the reason why we think it
1086    /// has those types.
1087    ///
1088    /// The `idx` must be an index from the `names` or `list_names` vectors.
1089    #[doc(hidden)]
1090    #[allow(clippy::only_used_in_recursion)] // hoi4 doesn't use data
1091    fn resolve_named(&self, idx: usize, data: &Everything) -> (Scopes, &Reason) {
1092        #[allow(clippy::indexing_slicing)]
1093        match self.named[idx] {
1094            ScopeEntry::Scope(s, ref reason) => (s, reason),
1095            ScopeEntry::Rootref => self.resolve_root(),
1096            ScopeEntry::Named(idx) => self.resolve_named(idx, data),
1097            ScopeEntry::Backref(_) => unreachable!(),
1098            #[cfg(feature = "hoi4")]
1099            ScopeEntry::Fromref(_) => unreachable!(),
1100            #[cfg(feature = "jomini")]
1101            ScopeEntry::GlobalVar(name, ref reason) => (data.global_scopes.scopes(name), reason),
1102            #[cfg(feature = "jomini")]
1103            ScopeEntry::GlobalList(name, ref reason) => {
1104                (data.global_list_scopes.scopes(name), reason)
1105            }
1106            #[cfg(feature = "jomini")]
1107            ScopeEntry::Var(name, ref reason) => (data.variable_scopes.scopes(name), reason),
1108            #[cfg(feature = "jomini")]
1109            ScopeEntry::VarList(name, ref reason) => {
1110                (data.variable_list_scopes.scopes(name), reason)
1111            }
1112        }
1113    }
1114
1115    /// Search through the scope levels to find out what a scope actually refers to.
1116    /// Pass 1 for `back` to examine the `this` scope.
1117    ///
1118    /// The returned `ScopeEntry` will not be a `ScopeEntry::Backref`.
1119    #[doc(hidden)]
1120    fn resolve_backrefs(&self, mut back: usize) -> &ScopeEntry {
1121        loop {
1122            // SAFETY: `replace_prev` ensures that when backrefs are added, enough scope
1123            // stack entries are added too.
1124            assert!(back > 0);
1125            assert!(back <= self.scope_stack.len());
1126            let entry = &self.scope_stack[self.scope_stack.len() - back];
1127            match entry {
1128                ScopeEntry::Backref(r) => back += *r,
1129                _ => {
1130                    return entry;
1131                }
1132            }
1133        }
1134    }
1135
1136    /// Search through the scope levels to find out what a scope actually refers to.
1137    /// Pass 1 for `back` to examine the `this` scope.
1138    ///
1139    /// The returned `ScopeEntry` will not be a `ScopeEntry::Backref`.
1140    #[doc(hidden)]
1141    fn resolve_backrefs_mut(&mut self, mut back: usize) -> &mut ScopeEntry {
1142        // TODO: can the duplication with resolve_backrefs be avoided?
1143        loop {
1144            // SAFETY: `replace_prev` ensures that when backrefs are added, enough scope
1145            // stack entries are added too.
1146            assert!(back > 0);
1147            assert!(back <= self.scope_stack.len());
1148            let idx = self.scope_stack.len() - back;
1149            match self.scope_stack[idx] {
1150                ScopeEntry::Backref(r) => back += r,
1151                _ => {
1152                    return &mut self.scope_stack[idx];
1153                }
1154            }
1155        }
1156    }
1157
1158    /// Return the possible scope types for the current scope layer, together with the reason why
1159    /// we think that.
1160    pub fn scopes_reason(&self, data: &Everything) -> (Scopes, &Reason) {
1161        self.scopes_reason_backref(THIS, data)
1162    }
1163
1164    /// Return the possible scope types for a `from` entry, together with the reason why we think
1165    /// that.
1166    #[cfg(feature = "hoi4")]
1167    #[doc(hidden)]
1168    fn resolve_from(&self, back: usize) -> (Scopes, &Reason) {
1169        match self.from.get(back) {
1170            None => {
1171                // We went further up the FROM chain than we know about.
1172                let scopes = if self.strict_scopes { Scopes::None } else { Scopes::all() };
1173                // just nab the `reason` from root
1174                match self.root {
1175                    ScopeEntry::Scope(_, ref reason) => (scopes, reason),
1176                    _ => unreachable!(),
1177                }
1178            }
1179            Some(ScopeEntry::Scope(s, reason)) => (*s, reason),
1180            Some(_) => unreachable!(),
1181        }
1182    }
1183
1184    #[doc(hidden)]
1185    fn scopes_reason_backref(&self, back: usize, data: &Everything) -> (Scopes, &Reason) {
1186        match self.resolve_backrefs(back) {
1187            ScopeEntry::Scope(s, reason) => (*s, reason),
1188            ScopeEntry::Backref(_) => unreachable!(),
1189            #[cfg(feature = "hoi4")]
1190            ScopeEntry::Fromref(r) => self.resolve_from(*r),
1191            ScopeEntry::Rootref => self.resolve_root(),
1192            ScopeEntry::Named(idx) => self.resolve_named(*idx, data),
1193            #[cfg(feature = "jomini")]
1194            ScopeEntry::GlobalVar(name, reason) => (data.global_scopes.scopes(name), reason),
1195            #[cfg(feature = "jomini")]
1196            ScopeEntry::GlobalList(name, reason) => (data.global_list_scopes.scopes(name), reason),
1197            #[cfg(feature = "jomini")]
1198            ScopeEntry::Var(name, reason) => (data.variable_scopes.scopes(name), reason),
1199            #[cfg(feature = "jomini")]
1200            ScopeEntry::VarList(name, reason) => (data.variable_list_scopes.scopes(name), reason),
1201        }
1202    }
1203
1204    /// Add messages to a report that describe where this `ScopeContext` came from.
1205    pub fn log_traceback(&self, mut builder: ReportBuilderFull) -> ReportBuilderFull {
1206        for elem in self.traceback.iter().rev() {
1207            builder = builder.loc_msg(elem.token(), "triggered from here");
1208        }
1209        builder.loc_msg(&self.source, "scopes initialized here")
1210    }
1211
1212    #[doc(hidden)]
1213    #[allow(unused_variables)] // hoi4 does not use data
1214    fn expect_check(e: &mut ScopeEntry, scopes: Scopes, reason: &Reason, data: &Everything) {
1215        match e {
1216            ScopeEntry::Scope(s, r) => {
1217                if s.intersects(scopes) {
1218                    // if s is narrowed by the scopes info, remember why
1219                    if (*s & scopes) != *s {
1220                        *s &= scopes;
1221                        *r = reason.clone();
1222                    }
1223                } else {
1224                    let token = reason.token();
1225                    let msg = format!("`{token}` is for {scopes} but scope seems to be {s}");
1226                    let msg2 = format!("scope was {}", r.msg());
1227                    warn(ErrorKey::Scopes).msg(msg).loc(token).loc_msg(r.token(), msg2).push();
1228                }
1229            }
1230            #[cfg(feature = "jomini")]
1231            ScopeEntry::GlobalVar(name, reason) => {
1232                data.global_scopes.expect(name, reason.token(), scopes);
1233            }
1234            #[cfg(feature = "jomini")]
1235            ScopeEntry::GlobalList(name, reason) => {
1236                data.global_list_scopes.expect(name, reason.token(), scopes);
1237            }
1238            #[cfg(feature = "jomini")]
1239            ScopeEntry::Var(name, reason) => {
1240                data.variable_scopes.expect(name, reason.token(), scopes);
1241            }
1242            #[cfg(feature = "jomini")]
1243            ScopeEntry::VarList(name, reason) => {
1244                data.variable_list_scopes.expect(name, reason.token(), scopes);
1245            }
1246            _ => unreachable!(),
1247        }
1248    }
1249
1250    #[doc(hidden)]
1251    #[allow(unused_variables)] // hoi4 does not use data
1252    fn expect_check3(
1253        e: &mut ScopeEntry,
1254        scopes: Scopes,
1255        reason: &Reason,
1256        key: &Token,
1257        report: &str,
1258        data: &Everything,
1259    ) {
1260        match e {
1261            ScopeEntry::Scope(s, r) => {
1262                if s.intersects(scopes) {
1263                    // if s is narrowed by the scopes info, remember its token
1264                    if (*s & scopes) != *s {
1265                        *s &= scopes;
1266                        *r = reason.clone();
1267                    }
1268                } else {
1269                    let msg = format!(
1270                        "`{key}` expects {report} to be {scopes} but {report} seems to be {s}"
1271                    );
1272                    let msg2 = format!("expected {report} was {}", reason.msg());
1273                    let msg3 = format!("actual {report} was {}", r.msg());
1274                    warn(ErrorKey::Scopes)
1275                        .msg(msg)
1276                        .loc(key)
1277                        .loc_msg(reason.token(), msg2)
1278                        .loc_msg(r.token(), msg3)
1279                        .push();
1280                }
1281            }
1282            #[cfg(feature = "jomini")]
1283            ScopeEntry::GlobalVar(name, reason) => {
1284                data.global_scopes.expect(name, reason.token(), scopes);
1285            }
1286            #[cfg(feature = "jomini")]
1287            ScopeEntry::GlobalList(name, reason) => {
1288                data.global_list_scopes.expect(name, reason.token(), scopes);
1289            }
1290            #[cfg(feature = "jomini")]
1291            ScopeEntry::Var(name, reason) => {
1292                data.variable_scopes.expect(name, reason.token(), scopes);
1293            }
1294            #[cfg(feature = "jomini")]
1295            ScopeEntry::VarList(name, reason) => {
1296                data.variable_list_scopes.expect(name, reason.token(), scopes);
1297            }
1298            _ => unreachable!(),
1299        }
1300    }
1301
1302    #[doc(hidden)]
1303    #[cfg(feature = "hoi4")]
1304    fn expect_fromref(&mut self, idx: usize, scopes: Scopes, reason: &Reason, data: &Everything) {
1305        if idx < self.from.len() {
1306            Self::expect_check(&mut self.from[idx], scopes, reason, data);
1307        }
1308    }
1309
1310    #[doc(hidden)]
1311    #[cfg(feature = "hoi4")]
1312    fn expect_fromref3(
1313        &mut self,
1314        idx: usize,
1315        scopes: Scopes,
1316        reason: &Reason,
1317        key: &Token,
1318        report: &str,
1319        data: &Everything,
1320    ) {
1321        if idx < self.from.len() {
1322            Self::expect_check3(&mut self.from[idx], scopes, reason, key, report, data);
1323        }
1324    }
1325
1326    // TODO: find a way to report the chain of Named tokens to the user
1327    #[doc(hidden)]
1328    fn expect_named(&mut self, mut idx: usize, scopes: Scopes, reason: &Reason, data: &Everything) {
1329        loop {
1330            #[allow(clippy::indexing_slicing)]
1331            match self.named[idx] {
1332                ScopeEntry::Scope(_, _) => {
1333                    Self::expect_check(&mut self.named[idx], scopes, reason, data);
1334                    return;
1335                }
1336                ScopeEntry::Rootref => {
1337                    Self::expect_check(&mut self.root, scopes, reason, data);
1338                    return;
1339                }
1340                ScopeEntry::Named(i) => idx = i,
1341                ScopeEntry::Backref(_) => unreachable!(),
1342                #[cfg(feature = "hoi4")]
1343                ScopeEntry::Fromref(_) => unreachable!(),
1344                #[cfg(feature = "jomini")]
1345                ScopeEntry::GlobalVar(_, _)
1346                | ScopeEntry::GlobalList(_, _)
1347                | ScopeEntry::Var(_, _)
1348                | ScopeEntry::VarList(_, _) => {
1349                    Self::expect_check(&mut self.named[idx], scopes, reason, data);
1350                    return;
1351                }
1352            }
1353        }
1354    }
1355
1356    #[doc(hidden)]
1357    fn expect_named3(
1358        &mut self,
1359        mut idx: usize,
1360        scopes: Scopes,
1361        reason: &Reason,
1362        key: &Token,
1363        report: &str,
1364        data: &Everything,
1365    ) {
1366        loop {
1367            #[allow(clippy::indexing_slicing)]
1368            match self.named[idx] {
1369                ScopeEntry::Scope(_, _) => {
1370                    Self::expect_check3(&mut self.named[idx], scopes, reason, key, report, data);
1371                    return;
1372                }
1373                ScopeEntry::Rootref => {
1374                    Self::expect_check3(&mut self.root, scopes, reason, key, report, data);
1375                    return;
1376                }
1377                ScopeEntry::Named(i) => idx = i,
1378                ScopeEntry::Backref(_) => unreachable!(),
1379                #[cfg(feature = "hoi4")]
1380                ScopeEntry::Fromref(_) => unreachable!(),
1381                #[cfg(feature = "jomini")]
1382                ScopeEntry::GlobalVar(_, _)
1383                | ScopeEntry::GlobalList(_, _)
1384                | ScopeEntry::Var(_, _)
1385                | ScopeEntry::VarList(_, _) => {
1386                    Self::expect_check3(&mut self.named[idx], scopes, reason, key, report, data);
1387                    return;
1388                }
1389            }
1390        }
1391    }
1392
1393    /// Record that the `this` in the current scope level is one of the scope types `scopes`, and
1394    /// if this is new information, record `token` as the reason we think that.
1395    /// Emit an error if what we already know about `this` is incompatible with `scopes`.
1396    pub fn expect(&mut self, scopes: Scopes, reason: &Reason, data: &Everything) {
1397        // The None scope is special, it means the scope isn't used or inspected
1398        if self.no_warn || scopes == Scopes::None {
1399            return;
1400        }
1401        let this = self.resolve_backrefs_mut(THIS);
1402        match this {
1403            ScopeEntry::Scope(_, _) => Self::expect_check(this, scopes, reason, data),
1404            ScopeEntry::Backref(_) => unreachable!(),
1405            #[cfg(feature = "hoi4")]
1406            &mut ScopeEntry::Fromref(r) => self.expect_fromref(r, scopes, reason, data),
1407            ScopeEntry::Rootref => Self::expect_check(&mut self.root, scopes, reason, data),
1408            &mut ScopeEntry::Named(idx) => self.expect_named(idx, scopes, reason, data),
1409            #[cfg(feature = "jomini")]
1410            ScopeEntry::GlobalVar(_, _)
1411            | ScopeEntry::GlobalList(_, _)
1412            | ScopeEntry::Var(_, _)
1413            | ScopeEntry::VarList(_, _) => Self::expect_check(this, scopes, reason, data),
1414        }
1415    }
1416
1417    /// Like [`Self::expect()`], but the error emitted will be located at token `key`.
1418    ///
1419    /// This function is used when the expectation of scope compatibility comes from `key`, for
1420    /// example when matching up a caller's scope context with a scripted effect's scope context.
1421    fn expect3(
1422        &mut self,
1423        scopes: Scopes,
1424        reason: &Reason,
1425        key: &Token,
1426        back: usize,
1427        report: &str,
1428        data: &Everything,
1429    ) {
1430        // The None scope is special, it means the scope isn't used or inspected
1431        if scopes == Scopes::None {
1432            return;
1433        }
1434        let this = self.resolve_backrefs_mut(back);
1435        match this {
1436            ScopeEntry::Scope(_, _) => Self::expect_check3(this, scopes, reason, key, report, data),
1437            ScopeEntry::Backref(_) => unreachable!(),
1438            #[cfg(feature = "hoi4")]
1439            &mut ScopeEntry::Fromref(r) => {
1440                self.expect_fromref3(r, scopes, reason, key, report, data);
1441            }
1442            ScopeEntry::Rootref => {
1443                Self::expect_check3(&mut self.root, scopes, reason, key, report, data);
1444            }
1445            &mut ScopeEntry::Named(idx) => {
1446                self.expect_named3(idx, scopes, reason, key, report, data);
1447            }
1448            #[cfg(feature = "jomini")]
1449            ScopeEntry::GlobalVar(_, _)
1450            | ScopeEntry::GlobalList(_, _)
1451            | ScopeEntry::Var(_, _)
1452            | ScopeEntry::VarList(_, _) => {
1453                Self::expect_check3(this, scopes, reason, key, report, data);
1454            }
1455        }
1456    }
1457
1458    /// Compare this scope context to `other`, with `key` as the token that identifies `other`.
1459    ///
1460    /// This function examines the `root`, `this`, `prev`, and named scopes of the two scope
1461    /// contexts and warns about any contradictions it finds.
1462    ///
1463    /// It expects `self` to be the caller and `other` to be the callee.
1464    pub fn expect_compatibility(&mut self, other: &ScopeContext, key: &Token, data: &Everything) {
1465        if self.no_warn {
1466            return;
1467        }
1468        // Compare restrictions on `root`
1469        match other.root {
1470            ScopeEntry::Scope(scopes, ref token) => {
1471                Self::expect_check3(&mut self.root, scopes, token, key, "root", data);
1472            }
1473            _ => unreachable!(),
1474        }
1475
1476        // Compare restrictions on `this`
1477        let (scopes, reason) = other.scopes_reason(data);
1478        self.expect3(scopes, reason, key, THIS, "scope", data);
1479
1480        // Compare restrictions on `prev`
1481        // TODO: for imperator and hoi4, go multiple prev levels back
1482        // TODO: The self.prev_levels > 0 check isn't right. Instead, create enough prev levels.
1483        if other.prev_levels > 0 && self.prev_levels > 0 {
1484            let (scopes, reason) = other.scopes_reason_backref(PREV, data);
1485            self.expect3(scopes, reason, key, PREV + usize::from(self.is_builder), "prev", data);
1486        }
1487
1488        // Compare restrictions on `from`
1489        // TODO: go all the way back up the chains
1490        #[cfg(feature = "hoi4")]
1491        {
1492            let (scopes, reason) = other.resolve_from(0);
1493            self.expect_fromref3(0, scopes, reason, key, "from", data);
1494            let (scopes, reason) = other.resolve_from(1);
1495            self.expect_fromref3(1, scopes, reason, key, "from.from", data);
1496        }
1497
1498        // Compare restrictions on named scopes
1499        for (name, &(oidx, otemp)) in &other.scope_names {
1500            if let Some((idx, t)) = self.scope_names.get(name).copied() {
1501                let (s, reason) = other.resolve_named(oidx, data);
1502                if other.is_input[oidx].is_some() {
1503                    let report = format!("scope:{name}");
1504                    if t == Temporary::Wiped {
1505                        let msg = format!(
1506                            "`{key}` expects {report} to be set but {report} was temporary and is no longer available here"
1507                        );
1508                        err(ErrorKey::TemporaryScope).weak().msg(msg).loc(key).push();
1509                    }
1510                    self.expect_named3(idx, s, reason, key, &report, data);
1511                } else {
1512                    // Their scopes now become our scopes.
1513                    self.scope_names.get_mut(name).unwrap().1 = otemp;
1514                    Self::break_chains_to(&mut self.named, idx);
1515                    self.named[idx] = ScopeEntry::Scope(s, reason.clone());
1516                }
1517            } else if self.strict_scopes && other.is_input[oidx].is_some() {
1518                let token = other.is_input[oidx].as_ref().unwrap();
1519                let msg = format!("`{key}` expects scope:{name} to be set");
1520                let msg2 = "here";
1521                self.log_traceback(
1522                    warn(ErrorKey::StrictScopes).msg(msg).loc(key).loc_msg(token, msg2),
1523                )
1524                .push();
1525            } else {
1526                // Their scopes now become our scopes.
1527                let (s, reason) = other.resolve_named(oidx, data);
1528                self.scope_names.insert(name, (self.named.len(), otemp));
1529                self.named.push(ScopeEntry::Scope(s, reason.clone()));
1530                self.is_input.push(other.is_input[oidx].clone());
1531            }
1532        }
1533
1534        // Compare restrictions on lists
1535        for (name, &(oidx, otemp)) in &other.scope_list_names {
1536            if let Some((idx, t)) = self.scope_list_names.get(name).copied() {
1537                let (s, reason) = other.resolve_named(oidx, data);
1538                if other.is_input[oidx].is_some() {
1539                    let report = format!("list {name}");
1540                    if t == Temporary::Wiped {
1541                        let msg = format!(
1542                            "`{key}` expects {report} to be set but {report} was temporary and is no longer available here"
1543                        );
1544                        err(ErrorKey::TemporaryScope).weak().msg(msg).loc(key).push();
1545                    }
1546                    self.expect_named3(idx, s, reason, key, &report, data);
1547                } else {
1548                    // Their lists now become our lists.
1549                    self.scope_list_names.get_mut(name).unwrap().1 = otemp;
1550                    Self::break_chains_to(&mut self.named, idx);
1551                    self.named[idx] = ScopeEntry::Scope(s, reason.clone());
1552                }
1553            } else if self.strict_scopes && other.is_input[oidx].is_some() {
1554                let token = other.is_input[oidx].as_ref().unwrap();
1555                let msg = format!("`{key}` expects list {name} to exist");
1556                let msg2 = "here";
1557                self.log_traceback(
1558                    warn(ErrorKey::StrictScopes).msg(msg).loc(key).loc_msg(token, msg2),
1559                )
1560                .push();
1561            } else {
1562                // Their lists now become our lists.
1563                let (s, reason) = other.resolve_named(oidx, data);
1564                self.scope_list_names.insert(name, (self.named.len(), otemp));
1565                self.named.push(ScopeEntry::Scope(s, reason.clone()));
1566                self.is_input.push(other.is_input[oidx].clone());
1567            }
1568        }
1569
1570        // Compare restrictions on local variables
1571        for (name, &oidx) in &other.local_names {
1572            if let Some(idx) = self.local_names.get(name).copied() {
1573                let (s, reason) = other.resolve_named(oidx, data);
1574                if other.is_input[oidx].is_some() {
1575                    let report = format!("local_var:{name}");
1576                    self.expect_named3(idx, s, reason, key, &report, data);
1577                } else {
1578                    // Their variables now become our variables
1579                    Self::break_chains_to(&mut self.named, idx);
1580                    self.named[idx] = ScopeEntry::Scope(s, reason.clone());
1581                }
1582            } else if self.strict_scopes && other.is_input[oidx].is_some() {
1583                let token = other.is_input[oidx].as_ref().unwrap();
1584                let msg = format!("`{key}` expects local_var:{name} to be set");
1585                let msg2 = "here";
1586                self.log_traceback(
1587                    warn(ErrorKey::StrictScopes).msg(msg).loc(key).loc_msg(token, msg2),
1588                )
1589                .push();
1590            } else {
1591                // Their variables now become our variables
1592                let (s, reason) = other.resolve_named(oidx, data);
1593                self.local_names.insert(name, self.named.len());
1594                self.named.push(ScopeEntry::Scope(s, reason.clone()));
1595                self.is_input.push(other.is_input[oidx].clone());
1596            }
1597        }
1598
1599        // Compare restrictions on local variable lists
1600        for (name, &oidx) in &other.local_list_names {
1601            if let Some(idx) = self.local_list_names.get(name).copied() {
1602                let (s, reason) = other.resolve_named(oidx, data);
1603                if other.is_input[oidx].is_some() {
1604                    let report = format!("local list {name}");
1605                    self.expect_named3(idx, s, reason, key, &report, data);
1606                } else {
1607                    // Their lists now become our lists.
1608                    Self::break_chains_to(&mut self.named, idx);
1609                    self.named[idx] = ScopeEntry::Scope(s, reason.clone());
1610                }
1611            } else if self.strict_scopes && other.is_input[oidx].is_some() {
1612                let token = other.is_input[oidx].as_ref().unwrap();
1613                let msg = format!("`{key}` expects local variable list {name} to exist");
1614                let msg2 = "here";
1615                self.log_traceback(
1616                    warn(ErrorKey::StrictScopes).msg(msg).loc(key).loc_msg(token, msg2),
1617                )
1618                .push();
1619            } else {
1620                // Their lists now become our lists.
1621                let (s, reason) = other.resolve_named(oidx, data);
1622                self.local_list_names.insert(name, self.named.len());
1623                self.named.push(ScopeEntry::Scope(s, reason.clone()));
1624                self.is_input.push(other.is_input[oidx].clone());
1625            }
1626        }
1627    }
1628
1629    /// Safely destroy a `ScopeContext` without fully unwinding its stack.
1630    /// This is useful when a `ScopeContext` needed to be cloned for some reason.
1631    #[allow(dead_code)]
1632    pub(crate) fn destroy(mut self) {
1633        self.prev_levels = self.scope_stack.len() - 1;
1634    }
1635}
1636
1637impl Drop for ScopeContext {
1638    /// This `drop` function checks that every opened scope level was also closed.
1639    fn drop(&mut self) {
1640        // Only assert if not already panicking, to avoid a double panic during unwind.
1641        if !panicking() {
1642            // Expect only the prev levels and the `this` level to remain.
1643            assert_eq!(
1644                self.scope_stack.len(),
1645                self.prev_levels + 1,
1646                "scope chain not properly unwound"
1647            );
1648        }
1649    }
1650}
1651
1652/// Deduce a scope type from a scope's name. This leads to better error messages.
1653///
1654/// It should be limited to names that are so obvious that it's extremely unlikely that anyone
1655/// would use them for a different type.
1656#[allow(unused_variables)] // hoi4 does not use `name`
1657#[allow(unused_mut)] // hoi4 does not use `name`
1658fn scope_type_from_name(mut name: &str) -> Option<Scopes> {
1659    #[cfg(feature = "jomini")]
1660    if let Some(real_name) = name.strip_prefix("scope:") {
1661        name = real_name;
1662    } else if let Some(real_name) = name.strip_prefix("local_var:") {
1663        name = real_name;
1664    } else {
1665        return None;
1666    }
1667
1668    #[cfg(feature = "ck3")]
1669    if Game::is_ck3() {
1670        return match name {
1671            "accolade" => Some(Scopes::Accolade),
1672            "accolade_type" => Some(Scopes::AccoladeType),
1673            "activity" => Some(Scopes::Activity),
1674            "actor"
1675            | "recipient"
1676            | "secondary_actor"
1677            | "secondary_recipient"
1678            | "mother"
1679            | "father"
1680            | "real_father"
1681            | "child"
1682            | "councillor"
1683            | "liege"
1684            | "courtier"
1685            | "guest"
1686            | "host" => Some(Scopes::Character),
1687            "army" => Some(Scopes::Army),
1688            "artifact" => Some(Scopes::Artifact),
1689            "barony" | "county" | "title" | "landed_title" => Some(Scopes::LandedTitle),
1690            "combat_side" => Some(Scopes::CombatSide),
1691            "council_task" => Some(Scopes::CouncilTask),
1692            "culture" => Some(Scopes::Culture),
1693            "faction" => Some(Scopes::Faction),
1694            "faith" => Some(Scopes::Faith),
1695            "province" => Some(Scopes::Province),
1696            "scheme" => Some(Scopes::Scheme),
1697            "struggle" => Some(Scopes::Struggle),
1698            "story" => Some(Scopes::StoryCycle),
1699            "travel_plan" => Some(Scopes::TravelPlan),
1700            "war" => Some(Scopes::War),
1701            _ => None,
1702        };
1703    }
1704
1705    #[cfg(feature = "vic3")]
1706    if Game::is_vic3() {
1707        // Due to differences in state vs state_region, law vs law_type, etc, less can be deduced
1708        // with certainty for vic3.
1709        return match name {
1710            "admiral" | "general" | "character" => Some(Scopes::Character),
1711            "actor" | "country" | "enemy_country" | "initiator" | "target_country" => {
1712                Some(Scopes::Country)
1713            }
1714            "battle" => Some(Scopes::Battle),
1715            "interest_group" => Some(Scopes::InterestGroup),
1716            "journal_entry" => Some(Scopes::JournalEntry),
1717            "market" => Some(Scopes::Market),
1718            _ => None,
1719        };
1720    }
1721
1722    #[cfg(feature = "imperator")]
1723    if Game::is_imperator() {
1724        return match name {
1725            "party" | "character_party" => Some(Scopes::Party),
1726            "employer" | "party_country" | "country" | "overlord" | "unit_owner"
1727            | "attacker_warleader" | "defender_warleader" | "former_overlord"
1728            | "target_subject" | "future_overlord" | "old_country" | "controller" | "owner"
1729            | "family_country" | "losing_side" | "home_country" => Some(Scopes::Country),
1730            "fam" | "family" => Some(Scopes::Family),
1731            "preferred_heir" | "deified_ruler" | "personal_loyalty" | "character"
1732            | "siege_controller" | "party_leader" | "next_in_family" | "ruler" | "governor"
1733            | "governor_or_ruler" | "commander" | "former_ruler" | "newborn" | "spouse"
1734            | "job_holder" | "consort" | "current_heir" | "current_ruler" | "primary_heir"
1735            | "secondary_heir" | "current_co_ruler" | "head_of_family" | "holding_owner"
1736            | "char" | "mother" | "father" => Some(Scopes::Character),
1737            "job" => Some(Scopes::Job),
1738            "legion" => Some(Scopes::Legion),
1739            "dominant_province_religion" | "religion" => Some(Scopes::Religion),
1740            "area" => Some(Scopes::Area),
1741            "region" => Some(Scopes::Region),
1742            "governorship" => Some(Scopes::Governorship),
1743            "country_culture" => Some(Scopes::CountryCulture),
1744            "location"
1745            | "unit_destination"
1746            | "unit_objective_destination"
1747            | "unit_location"
1748            | "unit_next_location"
1749            | "capital_scope"
1750            | "holy_site" => Some(Scopes::Province),
1751            "dominant_province_culture_group" | "culture_group" => Some(Scopes::CultureGroup),
1752            "dominant_province_culture" | "culture" => Some(Scopes::Culture),
1753            "owning_unit" => Some(Scopes::Unit),
1754            "deity" | "province_deity" => Some(Scopes::Deity),
1755            "state" => Some(Scopes::State),
1756            "treasure" => Some(Scopes::Treasure),
1757            "siege" => Some(Scopes::Siege),
1758            _ => None,
1759        };
1760    }
1761
1762    #[cfg(feature = "eu5")]
1763    if Game::is_eu5() {
1764        return match name {
1765            // TODO: EU5 fill in good guesses
1766            _ => None,
1767        };
1768    }
1769
1770    None
1771}