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}