use hir_lang::{ItemKind, Vis};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Namespace {
Value,
Type,
Module,
Macro,
Label,
Const,
}
impl Namespace {
pub const ALL: [Self; 6] = [
Self::Value,
Self::Type,
Self::Module,
Self::Macro,
Self::Label,
Self::Const,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Value => "value",
Self::Type => "type",
Self::Module => "module",
Self::Macro => "macro",
Self::Label => "label",
Self::Const => "constant",
}
}
#[inline]
pub(crate) const fn index(self) -> usize {
self as usize
}
pub(crate) const fn from_index(i: usize) -> Self {
match i {
0 => Self::Value,
1 => Self::Type,
2 => Self::Module,
3 => Self::Macro,
4 => Self::Label,
_ => Self::Const,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct NsSet(u8);
impl NsSet {
pub const EMPTY: Self = Self(0);
#[must_use]
pub const fn of(namespaces: &[Namespace]) -> Self {
let mut bits = 0u8;
let mut i = 0;
while i < namespaces.len() {
bits |= 1 << namespaces[i] as u8;
i += 1;
}
Self(bits)
}
#[must_use]
pub const fn single(ns: Namespace) -> Self {
Self(1 << ns as u8)
}
#[must_use]
pub const fn contains(self, ns: Namespace) -> bool {
self.0 & (1 << ns as u8) != 0
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn with(self, ns: Namespace) -> Self {
Self(self.0 | (1 << ns as u8))
}
pub fn iter(self) -> impl Iterator<Item = Namespace> {
Namespace::ALL
.into_iter()
.filter(move |ns| self.contains(*ns))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ItemClass {
Fn,
Record,
Sum,
Class,
Interface,
Alias,
AssocType,
Const,
Global,
Module,
Import,
}
impl ItemClass {
pub const ALL: [Self; 11] = [
Self::Fn,
Self::Record,
Self::Sum,
Self::Class,
Self::Interface,
Self::Alias,
Self::AssocType,
Self::Const,
Self::Global,
Self::Module,
Self::Import,
];
#[must_use]
pub const fn of(kind: &ItemKind) -> Option<Self> {
Some(match kind {
ItemKind::Fn(_) => Self::Fn,
ItemKind::Record(_) => Self::Record,
ItemKind::Sum(_) => Self::Sum,
ItemKind::Class(_) => Self::Class,
ItemKind::Interface(_) => Self::Interface,
ItemKind::Alias { .. } => Self::Alias,
ItemKind::AssocType { .. } => Self::AssocType,
ItemKind::Const { .. } => Self::Const,
ItemKind::Global { .. } => Self::Global,
ItemKind::Module { .. } => Self::Module,
ItemKind::Import { .. } => Self::Import,
ItemKind::Impl(_) | ItemKind::MixinUse(_) | ItemKind::Err => return None,
})
}
#[inline]
const fn index(self) -> usize {
self as usize
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Hoist {
Scope,
AfterDecl,
Module,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Shadowing {
Allow,
DenySameScope,
DenyLocals,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Redefinition {
Error,
LastWins,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ClassScope {
Lexical,
BodyOnly,
Qualified,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ModuleScope {
Isolated,
Lexical,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RootBinding {
Early,
Late,
Unsupported,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Reexport {
AsDeclared,
Always,
Never,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Case {
Sensitive,
AsciiInsensitive,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Policy {
hoist: [Hoist; 11],
occupies: [NsSet; 11],
merge: [Namespace; 6],
case: [Case; 6],
shadowing: Shadowing,
redefinition: Redefinition,
class_scope: ClassScope,
module_scope: ModuleScope,
roots: [RootBinding; 3],
enforce_visibility: bool,
private_to_descendants: bool,
globs: bool,
aliases: bool,
reexport: Reexport,
implicit_globals: bool,
class_values: bool,
interface_members: bool,
}
impl Default for Policy {
fn default() -> Self {
Self::new()
}
}
impl Policy {
#[must_use]
pub const fn new() -> Self {
use Namespace::{Const, Label, Macro, Module, Type, Value};
let value = NsSet::single(Value);
let ty = NsSet::single(Type);
let both = NsSet::of(&[Type, Value]);
Self {
hoist: [Hoist::Scope; 11],
occupies: [
value,
both,
ty,
both,
ty,
ty,
ty,
NsSet::single(Const),
value,
NsSet::single(Module),
NsSet::EMPTY,
],
merge: [Value, Type, Type, Macro, Label, Value],
case: [Case::Sensitive; 6],
shadowing: Shadowing::Allow,
redefinition: Redefinition::Error,
class_scope: ClassScope::Lexical,
module_scope: ModuleScope::Isolated,
roots: [
RootBinding::Early,
RootBinding::Unsupported,
RootBinding::Unsupported,
],
enforce_visibility: true,
private_to_descendants: true,
globs: true,
aliases: true,
reexport: Reexport::AsDeclared,
implicit_globals: false,
class_values: false,
interface_members: false,
}
}
#[must_use]
pub const fn kraken() -> Self {
Self::new()
}
#[must_use]
pub const fn php() -> Self {
let mut p = Self::new();
let mut i = 0;
while i < 11 {
let c = ItemClass::ALL[i];
if matches!(
c,
ItemClass::Fn
| ItemClass::Record
| ItemClass::Sum
| ItemClass::Class
| ItemClass::Interface
| ItemClass::Const
) {
p.hoist[i] = Hoist::Module;
}
if matches!(
c,
ItemClass::Record | ItemClass::Sum | ItemClass::Class | ItemClass::Interface
) {
p.occupies[i] = NsSet::single(Namespace::Type);
}
i += 1;
}
p.occupies[ItemClass::Global.index()] = NsSet::single(Namespace::Const);
p.merge = [
Namespace::Value,
Namespace::Type,
Namespace::Type,
Namespace::Macro,
Namespace::Label,
Namespace::Const,
];
p.case[Namespace::Value.index()] = Case::AsciiInsensitive;
p.case[Namespace::Type.index()] = Case::AsciiInsensitive;
p.class_scope = ClassScope::Qualified;
p.module_scope = ModuleScope::Lexical;
p.roots = [RootBinding::Early, RootBinding::Early, RootBinding::Late];
p.globs = false;
p.reexport = Reexport::Never;
p.implicit_globals = true;
p.class_values = true;
p.interface_members = true;
p
}
#[must_use]
pub const fn python() -> Self {
let mut p = Self::new();
p.merge = [
Namespace::Value,
Namespace::Value,
Namespace::Value,
Namespace::Value,
Namespace::Label,
Namespace::Value,
];
p.redefinition = Redefinition::LastWins;
p.class_scope = ClassScope::BodyOnly;
p.module_scope = ModuleScope::Lexical;
p.roots = [RootBinding::Unsupported; 3];
p.enforce_visibility = false;
p.reexport = Reexport::Always;
p.implicit_globals = true;
p
}
#[must_use]
pub const fn with_hoisting(mut self, class: ItemClass, hoist: Hoist) -> Self {
self.hoist[class.index()] = hoist;
self
}
#[must_use]
pub const fn with_occupies(mut self, class: ItemClass, set: NsSet) -> Self {
self.occupies[class.index()] = set;
self
}
#[must_use]
pub const fn with_merge(mut self, from: Namespace, into: Namespace) -> Self {
let old = self.merge[from.index()];
let new = self.merge[into.index()];
let mut i = 0;
while i < 6 {
if self.merge[i] as u8 == old as u8 {
self.merge[i] = new;
}
i += 1;
}
self
}
#[must_use]
pub const fn with_case(mut self, ns: Namespace, case: Case) -> Self {
self.case[self.merge[ns.index()].index()] = case;
self
}
#[must_use]
pub const fn with_shadowing(mut self, shadowing: Shadowing) -> Self {
self.shadowing = shadowing;
self
}
#[must_use]
pub const fn with_redefinition(mut self, redefinition: Redefinition) -> Self {
self.redefinition = redefinition;
self
}
#[must_use]
pub const fn with_class_scope(mut self, class_scope: ClassScope) -> Self {
self.class_scope = class_scope;
self
}
#[must_use]
pub const fn with_module_scope(mut self, module_scope: ModuleScope) -> Self {
self.module_scope = module_scope;
self
}
#[must_use]
pub const fn with_roots(
mut self,
self_root: RootBinding,
parent_root: RootBinding,
static_root: RootBinding,
) -> Self {
self.roots = [self_root, parent_root, static_root];
self
}
#[must_use]
pub const fn with_visibility(mut self, enforce: bool, private_to_descendants: bool) -> Self {
self.enforce_visibility = enforce;
self.private_to_descendants = private_to_descendants;
self
}
#[must_use]
pub const fn with_imports(mut self, globs: bool, aliases: bool, reexport: Reexport) -> Self {
self.globs = globs;
self.aliases = aliases;
self.reexport = reexport;
self
}
#[must_use]
pub const fn with_implicit_globals(mut self, implicit: bool) -> Self {
self.implicit_globals = implicit;
self
}
#[must_use]
pub const fn with_class_values(mut self, on: bool) -> Self {
self.class_values = on;
self
}
#[must_use]
pub const fn with_interface_members(mut self, on: bool) -> Self {
self.interface_members = on;
self
}
#[must_use]
pub const fn hoisting(&self, class: ItemClass) -> Hoist {
self.hoist[class.index()]
}
#[must_use]
pub const fn occupies(&self, class: ItemClass) -> NsSet {
self.occupies[class.index()]
}
#[must_use]
pub const fn table(&self, ns: Namespace) -> Namespace {
self.merge[ns.index()]
}
#[must_use]
pub const fn case(&self, ns: Namespace) -> Case {
self.case[self.merge[ns.index()].index()]
}
#[must_use]
pub const fn shadowing(&self) -> Shadowing {
self.shadowing
}
#[must_use]
pub const fn redefinition(&self) -> Redefinition {
self.redefinition
}
#[must_use]
pub const fn class_scope(&self) -> ClassScope {
self.class_scope
}
#[must_use]
pub const fn module_scope(&self) -> ModuleScope {
self.module_scope
}
#[must_use]
pub const fn self_root(&self) -> RootBinding {
self.roots[0]
}
#[must_use]
pub const fn parent_root(&self) -> RootBinding {
self.roots[1]
}
#[must_use]
pub const fn static_root(&self) -> RootBinding {
self.roots[2]
}
#[must_use]
pub const fn visibility_enforced(&self) -> bool {
self.enforce_visibility
}
#[must_use]
pub const fn private_to_descendants(&self) -> bool {
self.private_to_descendants
}
#[must_use]
pub const fn globs_allowed(&self) -> bool {
self.globs
}
#[must_use]
pub const fn aliases_allowed(&self) -> bool {
self.aliases
}
#[must_use]
pub const fn reexport(&self) -> Reexport {
self.reexport
}
#[must_use]
pub const fn implicit_globals(&self) -> bool {
self.implicit_globals
}
#[must_use]
pub const fn class_values(&self) -> bool {
self.class_values
}
#[must_use]
pub const fn interface_members(&self) -> bool {
self.interface_members
}
pub(crate) const fn import_vis(&self, declared: Vis) -> Vis {
match self.reexport {
Reexport::AsDeclared => declared,
Reexport::Always => Vis::Public,
Reexport::Never => Vis::Private,
}
}
#[inline]
pub(crate) const fn table_ix(&self, ns: Namespace) -> u8 {
self.merge[ns.index()] as u8
}
#[inline]
pub(crate) const fn table_folds(&self, t: u8) -> bool {
(t as usize) < TABLES && !matches!(self.case[t as usize], Case::Sensitive)
}
pub(crate) const fn any_folds(&self) -> bool {
let mut t = 0u8;
while (t as usize) < TABLES {
if self.table_folds(t) {
return true;
}
t += 1;
}
false
}
}
pub(crate) const TABLES: usize = 6;
pub(crate) const fn vis_rank(v: Vis) -> u8 {
match v {
Vis::Private => 0,
Vis::Protected => 1,
Vis::Package => 2,
Vis::Public => 3,
}
}
pub(crate) const fn min_vis(a: Vis, b: Vis) -> Vis {
if vis_rank(a) <= vis_rank(b) { a } else { b }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_merge_is_transitive() {
let p = Policy::new()
.with_merge(Namespace::Macro, Namespace::Value)
.with_merge(Namespace::Value, Namespace::Type);
assert_eq!(p.table(Namespace::Macro), Namespace::Type);
assert_eq!(p.table(Namespace::Value), Namespace::Type);
assert_eq!(p.table(Namespace::Module), Namespace::Type);
assert_eq!(p.table(Namespace::Label), Namespace::Label);
}
#[test]
fn test_min_vis_orders_private_lowest() {
assert_eq!(min_vis(Vis::Public, Vis::Private), Vis::Private);
assert_eq!(min_vis(Vis::Package, Vis::Public), Vis::Package);
assert_eq!(min_vis(Vis::Protected, Vis::Package), Vis::Protected);
}
#[test]
fn test_import_vis_follows_reexport() {
assert_eq!(Policy::new().import_vis(Vis::Public), Vis::Public);
assert_eq!(Policy::new().import_vis(Vis::Private), Vis::Private);
assert_eq!(Policy::python().import_vis(Vis::Private), Vis::Public);
assert_eq!(Policy::php().import_vis(Vis::Public), Vis::Private);
}
#[test]
fn test_class_settings_are_php_only_and_independent() {
for p in [Policy::new(), Policy::kraken(), Policy::python()] {
assert!(!p.class_values() && !p.interface_members());
}
let php = Policy::php();
assert!(php.class_values() && php.interface_members());
let p = php.with_class_values(false);
assert!(!p.class_values() && p.interface_members());
let p = php.with_interface_members(false);
assert!(p.class_values() && !p.interface_members());
assert_ne!(php, php.with_class_values(false));
assert_ne!(Policy::new(), Policy::new().with_interface_members(true));
}
#[test]
fn test_namespace_index_round_trips() {
for ns in Namespace::ALL {
assert_eq!(Namespace::from_index(ns.index()), ns);
}
}
}