Skip to main content

resolve_lang/
policy.rs

1//! The scoping policy: how one language binds names, as data.
2//!
3//! Every forged language reaches resolve-lang as HIR, so the differences
4//! between, say, PHP, Python, and Kraken are not separate resolvers but
5//! settings: when an item becomes visible (hoisting), whether a binder may
6//! reuse a visible name (shadowing), which namespaces exist and which share a
7//! table (merging), what a method body can see of its class, how the
8//! late-static-binding roots bind, and how imports and visibility behave.
9
10use hir_lang::{ItemKind, Vis};
11
12/// One of the language-level namespaces a name can live in.
13///
14/// HIR paths carry a syntactic namespace ([`hir_lang::Ns`]); these are the
15/// *tables* names are stored in. A language merges tables with
16/// [`Policy::with_merge`] (Python keeps everything in one table; Rust keeps types
17/// and modules in one; by default constants share the value table, PHP keeps
18/// them apart).
19///
20/// # Examples
21///
22/// ```
23/// use resolve_lang::Namespace;
24///
25/// assert_eq!(Namespace::Value.name(), "value");
26/// assert_eq!(Namespace::ALL.len(), 6);
27/// ```
28#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub enum Namespace {
30    /// Functions, constants, globals, constructors.
31    Value,
32    /// Records, sums, classes, interfaces, aliases.
33    Type,
34    /// Modules and units.
35    Module,
36    /// Macros. HIR has no macro items (macros expand before lowering); hosts
37    /// may still export names here through [`Env`](crate::Env).
38    Macro,
39    /// Loop and block labels. Labels are binders resolved by lowering, so no
40    /// HIR path is looked up here; the namespace exists for merging rules.
41    Label,
42    /// Constants (and, under [`Policy::php`], globals and static properties):
43    /// the names a language keeps apart from its functions. Merged into
44    /// [`Value`](Self::Value) unless the policy separates it, as PHP does: a
45    /// PHP function and a constant (or a method and a class constant) may
46    /// share a name. A value path that is the callee of a call searches the
47    /// value table first and this one second; any other value path searches
48    /// this one first.
49    Const,
50}
51
52impl Namespace {
53    /// Every namespace, in table order.
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// use resolve_lang::Namespace;
59    ///
60    /// assert_eq!(Namespace::ALL[0], Namespace::Value);
61    /// ```
62    pub const ALL: [Self; 6] = [
63        Self::Value,
64        Self::Type,
65        Self::Module,
66        Self::Macro,
67        Self::Label,
68        Self::Const,
69    ];
70
71    /// The lowercase name, for messages.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use resolve_lang::Namespace;
77    ///
78    /// assert_eq!(Namespace::Module.name(), "module");
79    /// ```
80    #[must_use]
81    pub const fn name(self) -> &'static str {
82        match self {
83            Self::Value => "value",
84            Self::Type => "type",
85            Self::Module => "module",
86            Self::Macro => "macro",
87            Self::Label => "label",
88            Self::Const => "constant",
89        }
90    }
91
92    #[inline]
93    pub(crate) const fn index(self) -> usize {
94        self as usize
95    }
96
97    pub(crate) const fn from_index(i: usize) -> Self {
98        match i {
99            0 => Self::Value,
100            1 => Self::Type,
101            2 => Self::Module,
102            3 => Self::Macro,
103            4 => Self::Label,
104            _ => Self::Const,
105        }
106    }
107}
108
109/// A set of [`Namespace`]s, as a bit set.
110///
111/// # Examples
112///
113/// ```
114/// use resolve_lang::{Namespace, NsSet};
115///
116/// let set = NsSet::of(&[Namespace::Type, Namespace::Value]);
117/// assert!(set.contains(Namespace::Type));
118/// assert!(!set.contains(Namespace::Module));
119/// assert_eq!(set.iter().count(), 2);
120/// ```
121#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
122pub struct NsSet(u8);
123
124impl NsSet {
125    /// The empty set.
126    ///
127    /// # Examples
128    ///
129    /// ```
130    /// use resolve_lang::NsSet;
131    ///
132    /// assert!(NsSet::EMPTY.is_empty());
133    /// ```
134    pub const EMPTY: Self = Self(0);
135
136    /// A set holding exactly `namespaces`.
137    ///
138    /// # Examples
139    ///
140    /// ```
141    /// use resolve_lang::{Namespace, NsSet};
142    ///
143    /// assert_eq!(NsSet::of(&[Namespace::Value]), NsSet::single(Namespace::Value));
144    /// ```
145    #[must_use]
146    pub const fn of(namespaces: &[Namespace]) -> Self {
147        let mut bits = 0u8;
148        let mut i = 0;
149        while i < namespaces.len() {
150            bits |= 1 << namespaces[i] as u8;
151            i += 1;
152        }
153        Self(bits)
154    }
155
156    /// A set holding one namespace.
157    ///
158    /// # Examples
159    ///
160    /// ```
161    /// use resolve_lang::{Namespace, NsSet};
162    ///
163    /// assert!(NsSet::single(Namespace::Macro).contains(Namespace::Macro));
164    /// ```
165    #[must_use]
166    pub const fn single(ns: Namespace) -> Self {
167        Self(1 << ns as u8)
168    }
169
170    /// Whether `ns` is in the set.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use resolve_lang::{Namespace, NsSet};
176    ///
177    /// assert!(!NsSet::EMPTY.contains(Namespace::Value));
178    /// ```
179    #[must_use]
180    pub const fn contains(self, ns: Namespace) -> bool {
181        self.0 & (1 << ns as u8) != 0
182    }
183
184    /// Whether the set is empty.
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// use resolve_lang::{Namespace, NsSet};
190    ///
191    /// assert!(!NsSet::single(Namespace::Type).is_empty());
192    /// ```
193    #[must_use]
194    pub const fn is_empty(self) -> bool {
195        self.0 == 0
196    }
197
198    /// The set with `ns` added.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use resolve_lang::{Namespace, NsSet};
204    ///
205    /// let s = NsSet::EMPTY.with(Namespace::Type);
206    /// assert!(s.contains(Namespace::Type));
207    /// ```
208    #[must_use]
209    pub const fn with(self, ns: Namespace) -> Self {
210        Self(self.0 | (1 << ns as u8))
211    }
212
213    /// The namespaces in the set, in table order.
214    ///
215    /// # Examples
216    ///
217    /// ```
218    /// use resolve_lang::{Namespace, NsSet};
219    ///
220    /// let v: Vec<_> = NsSet::of(&[Namespace::Module, Namespace::Value]).iter().collect();
221    /// assert_eq!(v, [Namespace::Value, Namespace::Module]);
222    /// ```
223    pub fn iter(self) -> impl Iterator<Item = Namespace> {
224        Namespace::ALL
225            .into_iter()
226            .filter(move |ns| self.contains(*ns))
227    }
228}
229
230/// The kinds of named items a policy distinguishes.
231///
232/// # Examples
233///
234/// ```
235/// use resolve_lang::ItemClass;
236///
237/// assert_eq!(ItemClass::ALL.len(), 11);
238/// ```
239#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
240pub enum ItemClass {
241    /// A function or method.
242    Fn,
243    /// A record (struct).
244    Record,
245    /// A sum (enum).
246    Sum,
247    /// A class (or PHP trait, a mixin class).
248    Class,
249    /// An interface or trait.
250    Interface,
251    /// A type alias.
252    Alias,
253    /// An associated type.
254    AssocType,
255    /// A constant.
256    Const,
257    /// A global variable (a static, a Python module-level variable).
258    Global,
259    /// A module.
260    Module,
261    /// An import (`use`, `import`, `from … import`).
262    Import,
263}
264
265impl ItemClass {
266    /// Every item class.
267    ///
268    /// # Examples
269    ///
270    /// ```
271    /// use resolve_lang::ItemClass;
272    ///
273    /// assert_eq!(ItemClass::ALL[0], ItemClass::Fn);
274    /// ```
275    pub const ALL: [Self; 11] = [
276        Self::Fn,
277        Self::Record,
278        Self::Sum,
279        Self::Class,
280        Self::Interface,
281        Self::Alias,
282        Self::AssocType,
283        Self::Const,
284        Self::Global,
285        Self::Module,
286        Self::Import,
287    ];
288
289    /// The class of a HIR item kind, or `None` for kinds that bind no name
290    /// (`impl`, mixin use, error items).
291    ///
292    /// # Examples
293    ///
294    /// ```
295    /// use hir_lang::{FnDef, ItemKind};
296    /// use resolve_lang::ItemClass;
297    ///
298    /// assert_eq!(ItemClass::of(&ItemKind::Fn(FnDef::default())), Some(ItemClass::Fn));
299    /// assert_eq!(ItemClass::of(&ItemKind::Err), None);
300    /// ```
301    #[must_use]
302    pub const fn of(kind: &ItemKind) -> Option<Self> {
303        Some(match kind {
304            ItemKind::Fn(_) => Self::Fn,
305            ItemKind::Record(_) => Self::Record,
306            ItemKind::Sum(_) => Self::Sum,
307            ItemKind::Class(_) => Self::Class,
308            ItemKind::Interface(_) => Self::Interface,
309            ItemKind::Alias { .. } => Self::Alias,
310            ItemKind::AssocType { .. } => Self::AssocType,
311            ItemKind::Const { .. } => Self::Const,
312            ItemKind::Global { .. } => Self::Global,
313            ItemKind::Module { .. } => Self::Module,
314            ItemKind::Import { .. } => Self::Import,
315            ItemKind::Impl(_) | ItemKind::MixinUse(_) | ItemKind::Err => return None,
316        })
317    }
318
319    #[inline]
320    const fn index(self) -> usize {
321        self as usize
322    }
323}
324
325/// When an item's name becomes visible in its scope.
326///
327/// # Examples
328///
329/// ```
330/// use resolve_lang::{Hoist, ItemClass, Policy};
331///
332/// // PHP: a function declared anywhere is visible in its whole namespace.
333/// assert_eq!(Policy::php().hoisting(ItemClass::Fn), Hoist::Module);
334/// ```
335#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
336pub enum Hoist {
337    /// Visible throughout the scope that declares it, before and after the
338    /// declaration (Rust and Kraken items, Python's per-scope binding rule).
339    Scope,
340    /// Visible from the declaration to the end of its scope, including inside
341    /// itself, so a function can recurse (C-style declare-before-use).
342    AfterDecl,
343    /// Visible throughout the nearest enclosing module, wherever it is
344    /// declared, even inside a function body or a conditional block (PHP
345    /// functions and classes).
346    Module,
347}
348
349/// Whether a binder may reuse a name that is already visible.
350///
351/// The rule is about binders a user declares. A place alias (the binder of a
352/// HIR `let_place`, which lowering introduces for a compound assignment) is
353/// exempt: it is never reported, and a local declared inside its body is not
354/// reported for reusing its name.
355///
356/// # Examples
357///
358/// ```
359/// use resolve_lang::{Policy, Shadowing};
360///
361/// let p = Policy::new().with_shadowing(Shadowing::DenySameScope);
362/// assert_eq!(p.shadowing(), Shadowing::DenySameScope);
363/// ```
364#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
365pub enum Shadowing {
366    /// Any binder may shadow any visible name (Rust `let`, Python, PHP).
367    Allow,
368    /// A binder may not reuse a name bound earlier in the *same* scope
369    /// (JavaScript `let`, a parameter list with a repeated name).
370    DenySameScope,
371    /// A binder may not reuse the name of any local binder visible from the
372    /// same frame (C#, Zig).
373    DenyLocals,
374}
375
376/// What two definitions of one name in one scope and namespace mean.
377///
378/// # Examples
379///
380/// ```
381/// use resolve_lang::{Policy, Redefinition};
382///
383/// assert_eq!(Policy::python().redefinition(), Redefinition::LastWins);
384/// assert_eq!(Policy::kraken().redefinition(), Redefinition::Error);
385/// ```
386#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
387pub enum Redefinition {
388    /// A duplicate definition is an error; the first one is kept.
389    Error,
390    /// A later definition replaces an earlier one (Python `def f` twice).
391    /// With [`Hoist::AfterDecl`] each definition is visible from its own
392    /// declaration on.
393    LastWins,
394}
395
396/// What code nested in a class can see of the class's members unqualified.
397///
398/// # Examples
399///
400/// ```
401/// use resolve_lang::{ClassScope, Policy};
402///
403/// assert_eq!(Policy::python().class_scope(), ClassScope::BodyOnly);
404/// assert_eq!(Policy::php().class_scope(), ClassScope::Qualified);
405/// ```
406#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
407pub enum ClassScope {
408    /// Members are in lexical scope everywhere inside the class, methods
409    /// included (Java, C++, Kraken). Only the class's own members are seen
410    /// unqualified; inherited members go through `self::`/type-directed
411    /// lookup.
412    Lexical,
413    /// Members are visible to the class body's own initializers but not
414    /// inside methods, lambdas, or nested classes (Python).
415    BodyOnly,
416    /// Members are never visible unqualified; code writes `self::x`,
417    /// `static::x`, or `$this->x` (PHP).
418    Qualified,
419}
420
421/// Whether a nested module sees the names of the modules around it.
422///
423/// # Examples
424///
425/// ```
426/// use resolve_lang::{ModuleScope, Policy};
427///
428/// assert_eq!(Policy::kraken().module_scope(), ModuleScope::Isolated);
429/// ```
430#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
431pub enum ModuleScope {
432    /// A nested module starts a fresh lexical world: outer items are reached
433    /// through `super::` or an import (Rust, Kraken).
434    Isolated,
435    /// A nested module sees enclosing scopes' names (PHP namespaces falling
436    /// back outward, languages with lexically nested modules).
437    Lexical,
438}
439
440/// How a type-relative root (`self::`, `parent::`, `static::`) binds.
441///
442/// # Examples
443///
444/// ```
445/// use resolve_lang::{Policy, RootBinding};
446///
447/// // PHP: `static::` is late static binding, resolved at run time.
448/// assert_eq!(Policy::php().static_root(), RootBinding::Late);
449/// assert_eq!(Policy::php().self_root(), RootBinding::Early);
450/// ```
451#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
452pub enum RootBinding {
453    /// Bound at resolution time against the enclosing class (or its first
454    /// base, for `parent::`): a member found there is resolved now; one not
455    /// found is left to type-directed resolution.
456    Early,
457    /// Bound late (at run time or by the type checker): the whole path is
458    /// left type-directed (`unresolved = segments`).
459    Late,
460    /// The language has no such root; a path using it is reported.
461    Unsupported,
462}
463
464/// How an import's own visibility affects re-export.
465///
466/// # Examples
467///
468/// ```
469/// use resolve_lang::{Policy, Reexport};
470///
471/// assert_eq!(Policy::python().reexport(), Reexport::Always);
472/// ```
473#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
474pub enum Reexport {
475    /// An import is visible to other modules exactly as its item's [`Vis`]
476    /// says (`pub use` re-exports, plain `use` does not).
477    AsDeclared,
478    /// Every import becomes part of the module's exports (Python: an imported
479    /// name is a module attribute).
480    Always,
481    /// Imports are never exported (PHP: `use` is file-local).
482    Never,
483}
484
485/// How a namespace's table compares names.
486///
487/// Folding is a property of the *table*: every name stored in it, and every
488/// name looked up in it, is compared through the same fold. Folding is
489/// deterministic and independent of locale; diagnostics, the index, and
490/// [`ClassMember`](crate::ClassMember) keep each name as it was written.
491///
492/// Only ASCII folding is offered: it is what PHP 8 does for function,
493/// class, interface, trait, enum, and namespace names (bytes `A`-`Z` match
494/// `a`-`z`; every other byte, including UTF-8 sequences, must match
495/// exactly). Unicode case folding is not provided: no target language needs
496/// it, and its tables change with the Unicode version, which would make
497/// resolution depend on the toolchain.
498///
499/// # Examples
500///
501/// ```
502/// use resolve_lang::{Case, Namespace, Policy};
503///
504/// assert_eq!(Policy::php().case(Namespace::Value), Case::AsciiInsensitive);
505/// assert_eq!(Policy::php().case(Namespace::Const), Case::Sensitive);
506/// assert_eq!(Policy::python().case(Namespace::Value), Case::Sensitive);
507/// ```
508#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
509#[non_exhaustive]
510pub enum Case {
511    /// Names match only when spelled identically (the default everywhere).
512    Sensitive,
513    /// Names match when they are equal after mapping ASCII `A`-`Z` to
514    /// `a`-`z` (PHP 8).
515    AsciiInsensitive,
516}
517
518/// A language's complete scoping policy.
519///
520/// Start from a preset ([`kraken`](Self::kraken), [`php`](Self::php),
521/// [`python`](Self::python)) or [`new`](Self::new) (the lexical default, the
522/// same as Kraken), then adjust with the `with_*` setters. The policy is plain
523/// data, normally read from the language's sketch.
524///
525/// # Examples
526///
527/// ```
528/// use resolve_lang::{ClassScope, Hoist, ItemClass, Namespace, Policy};
529///
530/// let p = Policy::new()
531///     .with_hoisting(ItemClass::Fn, Hoist::AfterDecl)
532///     .with_class_scope(ClassScope::BodyOnly)
533///     .with_merge(Namespace::Type, Namespace::Value);
534/// assert_eq!(p.hoisting(ItemClass::Fn), Hoist::AfterDecl);
535/// assert_eq!(p.table(Namespace::Type), Namespace::Value);
536/// ```
537#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
538pub struct Policy {
539    hoist: [Hoist; 11],
540    occupies: [NsSet; 11],
541    merge: [Namespace; 6],
542    /// By table index (the namespace a table is merged into).
543    case: [Case; 6],
544    shadowing: Shadowing,
545    redefinition: Redefinition,
546    class_scope: ClassScope,
547    module_scope: ModuleScope,
548    roots: [RootBinding; 3],
549    enforce_visibility: bool,
550    private_to_descendants: bool,
551    globs: bool,
552    aliases: bool,
553    reexport: Reexport,
554    implicit_globals: bool,
555}
556
557impl Default for Policy {
558    fn default() -> Self {
559        Self::new()
560    }
561}
562
563impl Policy {
564    /// The lexical default (equal to [`kraken`](Self::kraken)): every item
565    /// hoisted to its scope; separate value, type, and macro tables with
566    /// modules sharing the type table and constants sharing the value table;
567    /// every table case-sensitive; class members (own and inherited) visible
568    /// in methods;
569    /// isolated nested modules; `Self::` early, `parent::`/`static::`
570    /// unsupported; visibility enforced with private items visible to child
571    /// modules; globs, aliases, and declared re-exports.
572    ///
573    /// # Examples
574    ///
575    /// ```
576    /// use resolve_lang::{ItemClass, Namespace, NsSet, Policy};
577    ///
578    /// let p = Policy::new();
579    /// assert_eq!(p.occupies(ItemClass::Record), NsSet::of(&[Namespace::Type, Namespace::Value]));
580    /// assert!(p.visibility_enforced());
581    /// ```
582    #[must_use]
583    pub const fn new() -> Self {
584        use Namespace::{Const, Label, Macro, Module, Type, Value};
585        let value = NsSet::single(Value);
586        let ty = NsSet::single(Type);
587        let both = NsSet::of(&[Type, Value]);
588        Self {
589            hoist: [Hoist::Scope; 11],
590            // Fn Record Sum Class Interface Alias AssocType Const Global Module Import
591            occupies: [
592                value,
593                both,
594                ty,
595                both,
596                ty,
597                ty,
598                ty,
599                NsSet::single(Const),
600                value,
601                NsSet::single(Module),
602                NsSet::EMPTY,
603            ],
604            merge: [Value, Type, Type, Macro, Label, Value],
605            case: [Case::Sensitive; 6],
606            shadowing: Shadowing::Allow,
607            redefinition: Redefinition::Error,
608            class_scope: ClassScope::Lexical,
609            module_scope: ModuleScope::Isolated,
610            roots: [
611                RootBinding::Early,
612                RootBinding::Unsupported,
613                RootBinding::Unsupported,
614            ],
615            enforce_visibility: true,
616            private_to_descendants: true,
617            globs: true,
618            aliases: true,
619            reexport: Reexport::AsDeclared,
620            implicit_globals: false,
621        }
622    }
623
624    /// Kraken and Iron: lexical scoping, modules with imports and
625    /// visibility. Equal to [`new`](Self::new).
626    ///
627    /// # Examples
628    ///
629    /// ```
630    /// use resolve_lang::Policy;
631    ///
632    /// assert_eq!(Policy::kraken(), Policy::new());
633    /// ```
634    #[must_use]
635    pub const fn kraken() -> Self {
636        Self::new()
637    }
638
639    /// PHP (and Mox): functions, classes, interfaces, enums, and constants are
640    /// visible in their whole namespace wherever declared; class members are
641    /// reached only through `self::`/`parent::`/`static::` (late static
642    /// binding for `static::`); `use` imports are file-local and have no
643    /// globs; `global $x` may name a global that does not exist yet.
644    ///
645    /// Names follow PHP 8's tables and case rules:
646    ///
647    /// | Table | Holds | Case |
648    /// |---|---|---|
649    /// | [`Value`](Namespace::Value) | functions, methods | ASCII-insensitive |
650    /// | [`Type`](Namespace::Type) (with [`Module`](Namespace::Module)) | classes, interfaces, traits, enums, namespaces | ASCII-insensitive |
651    /// | [`Const`](Namespace::Const) | constants, class constants, globals, static properties | sensitive |
652    ///
653    /// So a function and a constant, or a method and a class constant, may
654    /// share a name; `strlen` and `STRLEN` are one function; and a class is
655    /// a type only (construct it through a type path, as `new C` lowers).
656    /// HIR paths carry no `$` sigil, so a sketch that strips it from static
657    /// property names makes a static property and a class constant of the
658    /// same name collide; keep the sigil in property names (`$count`) to
659    /// keep them apart.
660    ///
661    /// # Examples
662    ///
663    /// ```
664    /// use resolve_lang::{Case, Hoist, ItemClass, Namespace, NsSet, Policy};
665    ///
666    /// let p = Policy::php();
667    /// assert_eq!(p.hoisting(ItemClass::Class), Hoist::Module);
668    /// assert_eq!(p.occupies(ItemClass::Class), NsSet::single(Namespace::Type));
669    /// assert_ne!(p.table(Namespace::Const), p.table(Namespace::Value));
670    /// assert_eq!(p.case(Namespace::Type), Case::AsciiInsensitive);
671    /// assert!(!p.globs_allowed());
672    /// assert!(p.implicit_globals());
673    /// ```
674    #[must_use]
675    pub const fn php() -> Self {
676        let mut p = Self::new();
677        let mut i = 0;
678        while i < 11 {
679            let c = ItemClass::ALL[i];
680            if matches!(
681                c,
682                ItemClass::Fn
683                    | ItemClass::Record
684                    | ItemClass::Sum
685                    | ItemClass::Class
686                    | ItemClass::Interface
687                    | ItemClass::Const
688            ) {
689                p.hoist[i] = Hoist::Module;
690            }
691            if matches!(
692                c,
693                ItemClass::Record | ItemClass::Sum | ItemClass::Class | ItemClass::Interface
694            ) {
695                p.occupies[i] = NsSet::single(Namespace::Type);
696            }
697            i += 1;
698        }
699        p.occupies[ItemClass::Global.index()] = NsSet::single(Namespace::Const);
700        p.merge = [
701            Namespace::Value,
702            Namespace::Type,
703            Namespace::Type,
704            Namespace::Macro,
705            Namespace::Label,
706            Namespace::Const,
707        ];
708        p.case[Namespace::Value.index()] = Case::AsciiInsensitive;
709        p.case[Namespace::Type.index()] = Case::AsciiInsensitive;
710        p.class_scope = ClassScope::Qualified;
711        p.module_scope = ModuleScope::Lexical;
712        p.roots = [RootBinding::Early, RootBinding::Early, RootBinding::Late];
713        p.globs = false;
714        p.reexport = Reexport::Never;
715        p.implicit_globals = true;
716        p
717    }
718
719    /// Python (and Mercury): one case-sensitive table for every kind of
720    /// name; a name bound anywhere in a scope belongs to the whole scope;
721    /// class bodies are not visible to methods; members are inherited in C3
722    /// method resolution order (as under every policy; an inconsistent
723    /// hierarchy is reported, as Python raises `TypeError` at class
724    /// creation); redefinition rebinds; visibility is convention only;
725    /// imports become module attributes.
726    ///
727    /// # Examples
728    ///
729    /// ```
730    /// use resolve_lang::{Namespace, Policy};
731    ///
732    /// let p = Policy::python();
733    /// assert_eq!(p.table(Namespace::Type), Namespace::Value);
734    /// assert_eq!(p.table(Namespace::Module), Namespace::Value);
735    /// assert!(!p.visibility_enforced());
736    /// ```
737    #[must_use]
738    pub const fn python() -> Self {
739        let mut p = Self::new();
740        p.merge = [
741            Namespace::Value,
742            Namespace::Value,
743            Namespace::Value,
744            Namespace::Value,
745            Namespace::Label,
746            Namespace::Value,
747        ];
748        p.redefinition = Redefinition::LastWins;
749        p.class_scope = ClassScope::BodyOnly;
750        p.module_scope = ModuleScope::Lexical;
751        p.roots = [RootBinding::Unsupported; 3];
752        p.enforce_visibility = false;
753        p.reexport = Reexport::Always;
754        p.implicit_globals = true;
755        p
756    }
757
758    // ------------------------------------------------------------ setters
759
760    /// Sets when items of `class` become visible.
761    ///
762    /// # Examples
763    ///
764    /// ```
765    /// use resolve_lang::{Hoist, ItemClass, Policy};
766    ///
767    /// let p = Policy::new().with_hoisting(ItemClass::Global, Hoist::AfterDecl);
768    /// assert_eq!(p.hoisting(ItemClass::Global), Hoist::AfterDecl);
769    /// ```
770    #[must_use]
771    pub const fn with_hoisting(mut self, class: ItemClass, hoist: Hoist) -> Self {
772        self.hoist[class.index()] = hoist;
773        self
774    }
775
776    /// Sets the namespaces items of `class` define their name in. Imports
777    /// ignore this (an import binds whatever its target defines).
778    ///
779    /// # Examples
780    ///
781    /// ```
782    /// use resolve_lang::{ItemClass, Namespace, NsSet, Policy};
783    ///
784    /// // Rust: a braced struct defines no constructor value.
785    /// let p = Policy::new().with_occupies(ItemClass::Record, NsSet::single(Namespace::Type));
786    /// assert!(!p.occupies(ItemClass::Record).contains(Namespace::Value));
787    /// ```
788    #[must_use]
789    pub const fn with_occupies(mut self, class: ItemClass, set: NsSet) -> Self {
790        self.occupies[class.index()] = set;
791        self
792    }
793
794    /// Merges namespace `from` into the table of `into`: afterwards both (and
795    /// everything already merged with either) share one table, so a name
796    /// defined in one conflicts with and is found by the other. The merged
797    /// table keeps `into`'s [`Case`].
798    ///
799    /// # Examples
800    ///
801    /// ```
802    /// use resolve_lang::{Namespace, Policy};
803    ///
804    /// let p = Policy::new().with_merge(Namespace::Value, Namespace::Type);
805    /// // Modules already share the type table, so all three are one table now.
806    /// assert_eq!(p.table(Namespace::Value), p.table(Namespace::Module));
807    /// ```
808    #[must_use]
809    pub const fn with_merge(mut self, from: Namespace, into: Namespace) -> Self {
810        let old = self.merge[from.index()];
811        let new = self.merge[into.index()];
812        let mut i = 0;
813        while i < 6 {
814            if self.merge[i] as u8 == old as u8 {
815                self.merge[i] = new;
816            }
817            i += 1;
818        }
819        self
820    }
821
822    /// Sets how the table of `ns` (and every namespace merged with it)
823    /// compares names. Merge first, then set the case: a later merge keeps
824    /// the target table's case.
825    ///
826    /// # Examples
827    ///
828    /// ```
829    /// use resolve_lang::{Case, Namespace, Policy};
830    ///
831    /// let p = Policy::new().with_case(Namespace::Type, Case::AsciiInsensitive);
832    /// // Modules share the type table, so they fold too.
833    /// assert_eq!(p.case(Namespace::Module), Case::AsciiInsensitive);
834    /// assert_eq!(p.case(Namespace::Value), Case::Sensitive);
835    /// ```
836    #[must_use]
837    pub const fn with_case(mut self, ns: Namespace, case: Case) -> Self {
838        self.case[self.merge[ns.index()].index()] = case;
839        self
840    }
841
842    /// Sets the shadowing rule for binders.
843    ///
844    /// # Examples
845    ///
846    /// ```
847    /// use resolve_lang::{Policy, Shadowing};
848    ///
849    /// assert_eq!(Policy::new().with_shadowing(Shadowing::DenyLocals).shadowing(), Shadowing::DenyLocals);
850    /// ```
851    #[must_use]
852    pub const fn with_shadowing(mut self, shadowing: Shadowing) -> Self {
853        self.shadowing = shadowing;
854        self
855    }
856
857    /// Sets what duplicate definitions mean.
858    ///
859    /// # Examples
860    ///
861    /// ```
862    /// use resolve_lang::{Policy, Redefinition};
863    ///
864    /// let p = Policy::new().with_redefinition(Redefinition::LastWins);
865    /// assert_eq!(p.redefinition(), Redefinition::LastWins);
866    /// ```
867    #[must_use]
868    pub const fn with_redefinition(mut self, redefinition: Redefinition) -> Self {
869        self.redefinition = redefinition;
870        self
871    }
872
873    /// Sets what code inside a class sees of its members.
874    ///
875    /// # Examples
876    ///
877    /// ```
878    /// use resolve_lang::{ClassScope, Policy};
879    ///
880    /// let p = Policy::new().with_class_scope(ClassScope::Qualified);
881    /// assert_eq!(p.class_scope(), ClassScope::Qualified);
882    /// ```
883    #[must_use]
884    pub const fn with_class_scope(mut self, class_scope: ClassScope) -> Self {
885        self.class_scope = class_scope;
886        self
887    }
888
889    /// Sets whether nested modules see outer names.
890    ///
891    /// # Examples
892    ///
893    /// ```
894    /// use resolve_lang::{ModuleScope, Policy};
895    ///
896    /// let p = Policy::new().with_module_scope(ModuleScope::Lexical);
897    /// assert_eq!(p.module_scope(), ModuleScope::Lexical);
898    /// ```
899    #[must_use]
900    pub const fn with_module_scope(mut self, module_scope: ModuleScope) -> Self {
901        self.module_scope = module_scope;
902        self
903    }
904
905    /// Sets how `self::`, `parent::`, and `static::` bind.
906    ///
907    /// # Examples
908    ///
909    /// ```
910    /// use resolve_lang::{Policy, RootBinding};
911    ///
912    /// let p = Policy::new().with_roots(RootBinding::Early, RootBinding::Early, RootBinding::Late);
913    /// assert_eq!(p.parent_root(), RootBinding::Early);
914    /// ```
915    #[must_use]
916    pub const fn with_roots(
917        mut self,
918        self_root: RootBinding,
919        parent_root: RootBinding,
920        static_root: RootBinding,
921    ) -> Self {
922        self.roots = [self_root, parent_root, static_root];
923        self
924    }
925
926    /// Sets whether visibility is enforced, and whether private items of a
927    /// module are visible to its descendant modules.
928    ///
929    /// # Examples
930    ///
931    /// ```
932    /// use resolve_lang::Policy;
933    ///
934    /// let p = Policy::new().with_visibility(true, false);
935    /// assert!(p.visibility_enforced());
936    /// assert!(!p.private_to_descendants());
937    /// ```
938    #[must_use]
939    pub const fn with_visibility(mut self, enforce: bool, private_to_descendants: bool) -> Self {
940        self.enforce_visibility = enforce;
941        self.private_to_descendants = private_to_descendants;
942        self
943    }
944
945    /// Sets which import forms the language has, and how imports re-export.
946    ///
947    /// # Examples
948    ///
949    /// ```
950    /// use resolve_lang::{Policy, Reexport};
951    ///
952    /// let p = Policy::new().with_imports(false, true, Reexport::Never);
953    /// assert!(!p.globs_allowed());
954    /// assert!(p.aliases_allowed());
955    /// ```
956    #[must_use]
957    pub const fn with_imports(mut self, globs: bool, aliases: bool, reexport: Reexport) -> Self {
958        self.globs = globs;
959        self.aliases = aliases;
960        self.reexport = reexport;
961        self
962    }
963
964    /// Sets whether a `global` declaration naming no known global binds a host
965    /// global (`Res::Extern`) instead of being reported.
966    ///
967    /// # Examples
968    ///
969    /// ```
970    /// use resolve_lang::Policy;
971    ///
972    /// assert!(Policy::new().with_implicit_globals(true).implicit_globals());
973    /// ```
974    #[must_use]
975    pub const fn with_implicit_globals(mut self, implicit: bool) -> Self {
976        self.implicit_globals = implicit;
977        self
978    }
979
980    // ------------------------------------------------------------ getters
981
982    /// When items of `class` become visible.
983    ///
984    /// # Examples
985    ///
986    /// ```
987    /// use resolve_lang::{Hoist, ItemClass, Policy};
988    ///
989    /// assert_eq!(Policy::new().hoisting(ItemClass::Fn), Hoist::Scope);
990    /// ```
991    #[must_use]
992    pub const fn hoisting(&self, class: ItemClass) -> Hoist {
993        self.hoist[class.index()]
994    }
995
996    /// The namespaces items of `class` define their name in (before merging).
997    ///
998    /// # Examples
999    ///
1000    /// ```
1001    /// use resolve_lang::{ItemClass, Namespace, Policy};
1002    ///
1003    /// assert!(Policy::new().occupies(ItemClass::Fn).contains(Namespace::Value));
1004    /// ```
1005    #[must_use]
1006    pub const fn occupies(&self, class: ItemClass) -> NsSet {
1007        self.occupies[class.index()]
1008    }
1009
1010    /// The table namespace `ns` is stored in after merging.
1011    ///
1012    /// # Examples
1013    ///
1014    /// ```
1015    /// use resolve_lang::{Namespace, Policy};
1016    ///
1017    /// assert_eq!(Policy::new().table(Namespace::Module), Namespace::Type);
1018    /// assert_eq!(Policy::new().table(Namespace::Value), Namespace::Value);
1019    /// ```
1020    #[must_use]
1021    pub const fn table(&self, ns: Namespace) -> Namespace {
1022        self.merge[ns.index()]
1023    }
1024
1025    /// How the table of `ns` compares names.
1026    ///
1027    /// # Examples
1028    ///
1029    /// ```
1030    /// use resolve_lang::{Case, Namespace, Policy};
1031    ///
1032    /// assert_eq!(Policy::new().case(Namespace::Value), Case::Sensitive);
1033    /// ```
1034    #[must_use]
1035    pub const fn case(&self, ns: Namespace) -> Case {
1036        self.case[self.merge[ns.index()].index()]
1037    }
1038
1039    /// The shadowing rule.
1040    ///
1041    /// # Examples
1042    ///
1043    /// ```
1044    /// use resolve_lang::{Policy, Shadowing};
1045    ///
1046    /// assert_eq!(Policy::new().shadowing(), Shadowing::Allow);
1047    /// ```
1048    #[must_use]
1049    pub const fn shadowing(&self) -> Shadowing {
1050        self.shadowing
1051    }
1052
1053    /// What duplicate definitions mean.
1054    ///
1055    /// # Examples
1056    ///
1057    /// ```
1058    /// use resolve_lang::{Policy, Redefinition};
1059    ///
1060    /// assert_eq!(Policy::php().redefinition(), Redefinition::Error);
1061    /// ```
1062    #[must_use]
1063    pub const fn redefinition(&self) -> Redefinition {
1064        self.redefinition
1065    }
1066
1067    /// What code inside a class sees of its members.
1068    ///
1069    /// # Examples
1070    ///
1071    /// ```
1072    /// use resolve_lang::{ClassScope, Policy};
1073    ///
1074    /// assert_eq!(Policy::new().class_scope(), ClassScope::Lexical);
1075    /// ```
1076    #[must_use]
1077    pub const fn class_scope(&self) -> ClassScope {
1078        self.class_scope
1079    }
1080
1081    /// Whether nested modules see outer names.
1082    ///
1083    /// # Examples
1084    ///
1085    /// ```
1086    /// use resolve_lang::{ModuleScope, Policy};
1087    ///
1088    /// assert_eq!(Policy::php().module_scope(), ModuleScope::Lexical);
1089    /// ```
1090    #[must_use]
1091    pub const fn module_scope(&self) -> ModuleScope {
1092        self.module_scope
1093    }
1094
1095    /// How `self::` binds.
1096    ///
1097    /// # Examples
1098    ///
1099    /// ```
1100    /// use resolve_lang::{Policy, RootBinding};
1101    ///
1102    /// assert_eq!(Policy::python().self_root(), RootBinding::Unsupported);
1103    /// ```
1104    #[must_use]
1105    pub const fn self_root(&self) -> RootBinding {
1106        self.roots[0]
1107    }
1108
1109    /// How `parent::` binds.
1110    ///
1111    /// # Examples
1112    ///
1113    /// ```
1114    /// use resolve_lang::{Policy, RootBinding};
1115    ///
1116    /// assert_eq!(Policy::kraken().parent_root(), RootBinding::Unsupported);
1117    /// ```
1118    #[must_use]
1119    pub const fn parent_root(&self) -> RootBinding {
1120        self.roots[1]
1121    }
1122
1123    /// How `static::` binds.
1124    ///
1125    /// # Examples
1126    ///
1127    /// ```
1128    /// use resolve_lang::{Policy, RootBinding};
1129    ///
1130    /// assert_eq!(Policy::php().static_root(), RootBinding::Late);
1131    /// ```
1132    #[must_use]
1133    pub const fn static_root(&self) -> RootBinding {
1134        self.roots[2]
1135    }
1136
1137    /// Whether visibility (`Vis`) is enforced.
1138    ///
1139    /// # Examples
1140    ///
1141    /// ```
1142    /// use resolve_lang::Policy;
1143    ///
1144    /// assert!(Policy::php().visibility_enforced());
1145    /// ```
1146    #[must_use]
1147    pub const fn visibility_enforced(&self) -> bool {
1148        self.enforce_visibility
1149    }
1150
1151    /// Whether private items of a module are visible in its descendant
1152    /// modules (Rust's rule).
1153    ///
1154    /// # Examples
1155    ///
1156    /// ```
1157    /// use resolve_lang::Policy;
1158    ///
1159    /// assert!(Policy::new().private_to_descendants());
1160    /// ```
1161    #[must_use]
1162    pub const fn private_to_descendants(&self) -> bool {
1163        self.private_to_descendants
1164    }
1165
1166    /// Whether glob imports are allowed.
1167    ///
1168    /// # Examples
1169    ///
1170    /// ```
1171    /// use resolve_lang::Policy;
1172    ///
1173    /// assert!(Policy::python().globs_allowed());
1174    /// ```
1175    #[must_use]
1176    pub const fn globs_allowed(&self) -> bool {
1177        self.globs
1178    }
1179
1180    /// Whether aliased imports (`use x as y`) are allowed.
1181    ///
1182    /// # Examples
1183    ///
1184    /// ```
1185    /// use resolve_lang::Policy;
1186    ///
1187    /// assert!(Policy::php().aliases_allowed());
1188    /// ```
1189    #[must_use]
1190    pub const fn aliases_allowed(&self) -> bool {
1191        self.aliases
1192    }
1193
1194    /// How imports re-export.
1195    ///
1196    /// # Examples
1197    ///
1198    /// ```
1199    /// use resolve_lang::{Policy, Reexport};
1200    ///
1201    /// assert_eq!(Policy::php().reexport(), Reexport::Never);
1202    /// ```
1203    #[must_use]
1204    pub const fn reexport(&self) -> Reexport {
1205        self.reexport
1206    }
1207
1208    /// Whether an unknown `global` name binds a host global.
1209    ///
1210    /// # Examples
1211    ///
1212    /// ```
1213    /// use resolve_lang::Policy;
1214    ///
1215    /// assert!(!Policy::kraken().implicit_globals());
1216    /// ```
1217    #[must_use]
1218    pub const fn implicit_globals(&self) -> bool {
1219        self.implicit_globals
1220    }
1221
1222    /// The export visibility of an import whose item says `declared`.
1223    pub(crate) const fn import_vis(&self, declared: Vis) -> Vis {
1224        match self.reexport {
1225            Reexport::AsDeclared => declared,
1226            Reexport::Always => Vis::Public,
1227            Reexport::Never => Vis::Private,
1228        }
1229    }
1230
1231    /// The table index (`0..TABLES`) of namespace `ns`.
1232    #[inline]
1233    pub(crate) const fn table_ix(&self, ns: Namespace) -> u8 {
1234        self.merge[ns.index()] as u8
1235    }
1236
1237    /// Whether table `t` (a table index) folds case.
1238    #[inline]
1239    pub(crate) const fn table_folds(&self, t: u8) -> bool {
1240        (t as usize) < TABLES && !matches!(self.case[t as usize], Case::Sensitive)
1241    }
1242
1243    /// Whether any table folds case.
1244    pub(crate) const fn any_folds(&self) -> bool {
1245        let mut t = 0u8;
1246        while (t as usize) < TABLES {
1247            if self.table_folds(t) {
1248                return true;
1249            }
1250            t += 1;
1251        }
1252        false
1253    }
1254}
1255
1256/// The number of name tables (one per [`Namespace`]).
1257pub(crate) const TABLES: usize = 6;
1258
1259/// The rank of a visibility, for "the smaller of two" (glob re-exports).
1260pub(crate) const fn vis_rank(v: Vis) -> u8 {
1261    match v {
1262        Vis::Private => 0,
1263        Vis::Protected => 1,
1264        Vis::Package => 2,
1265        Vis::Public => 3,
1266    }
1267}
1268
1269/// The narrower of two visibilities.
1270pub(crate) const fn min_vis(a: Vis, b: Vis) -> Vis {
1271    if vis_rank(a) <= vis_rank(b) { a } else { b }
1272}
1273
1274#[cfg(test)]
1275mod tests {
1276    use super::*;
1277
1278    #[test]
1279    fn test_merge_is_transitive() {
1280        let p = Policy::new()
1281            .with_merge(Namespace::Macro, Namespace::Value)
1282            .with_merge(Namespace::Value, Namespace::Type);
1283        assert_eq!(p.table(Namespace::Macro), Namespace::Type);
1284        assert_eq!(p.table(Namespace::Value), Namespace::Type);
1285        assert_eq!(p.table(Namespace::Module), Namespace::Type);
1286        assert_eq!(p.table(Namespace::Label), Namespace::Label);
1287    }
1288
1289    #[test]
1290    fn test_min_vis_orders_private_lowest() {
1291        assert_eq!(min_vis(Vis::Public, Vis::Private), Vis::Private);
1292        assert_eq!(min_vis(Vis::Package, Vis::Public), Vis::Package);
1293        assert_eq!(min_vis(Vis::Protected, Vis::Package), Vis::Protected);
1294    }
1295
1296    #[test]
1297    fn test_import_vis_follows_reexport() {
1298        assert_eq!(Policy::new().import_vis(Vis::Public), Vis::Public);
1299        assert_eq!(Policy::new().import_vis(Vis::Private), Vis::Private);
1300        assert_eq!(Policy::python().import_vis(Vis::Private), Vis::Public);
1301        assert_eq!(Policy::php().import_vis(Vis::Public), Vis::Private);
1302    }
1303
1304    #[test]
1305    fn test_namespace_index_round_trips() {
1306        for ns in Namespace::ALL {
1307            assert_eq!(Namespace::from_index(ns.index()), ns);
1308        }
1309    }
1310}