Skip to main content

yog_script_analyze/
index.rs

1//! The class index (`docs/design/ys.md` §8): what the compiler knows of the
2//! game for one Minecraft version — each class by Mojang's name, the side
3//! it's on, what it extends and implements, its fields and methods with
4//! their descriptors and access. Built by `yog index` from the game's own
5//! jars; the resolver and the type checker look the game's names up in it.
6
7use std::collections::HashMap;
8
9use yog_data::Schema;
10/// What separates a nested class from the class it's in (`Outer$Inner`).
11pub use yog_script_ids::jvm::NESTED;
12
13/// Where a class exists: on both sides, or only in the client's or the
14/// dedicated server's jar. Code of one side can't reach the other's.
15#[derive(Schema, Debug, Clone, Copy, Default, PartialEq, Eq)]
16pub enum Side {
17    #[default]
18    Shared,
19    Client,
20    Server,
21}
22
23/// A field or a method: its name, JVM descriptor (`(F)Z`, `I`) and access
24/// flags (`ACC_PUBLIC` 0x0001, `ACC_STATIC` 0x0008, …).
25#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
26pub struct Member {
27    pub name: String,
28    pub desc: String,
29    pub access: u16,
30}
31
32/// A class by its internal name (`net/minecraft/world/entity/LivingEntity`).
33#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
34pub struct Class {
35    pub name: String,
36    pub side: Side,
37    pub access: u16,
38    pub extends: Option<String>,
39    pub implements: Vec<String>,
40    pub fields: Vec<Member>,
41    pub methods: Vec<Member>,
42}
43
44/// The game's classes for Minecraft `mc`.
45#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
46pub struct ClassIndex {
47    pub mc: String,
48    pub classes: Vec<Class>,
49}
50
51pub mod access;
52pub mod namespace;
53
54/// A [`ClassIndex`] ready to look things up in: classes by name, and by
55/// simple name (`LivingEntity`) for what a source writes unqualified.
56pub struct Lookup<'i> {
57    by_name: HashMap<&'i str, &'i Class>,
58    by_simple: HashMap<&'i str, Vec<&'i Class>>,
59}
60
61impl ClassIndex {
62    pub fn lookup(&self) -> Lookup<'_> {
63        let mut by_name = HashMap::new();
64        let mut by_simple: HashMap<&str, Vec<&Class>> = HashMap::new();
65        for c in &self.classes {
66            by_name.insert(c.name.as_str(), c);
67            by_simple.entry(simple_name(&c.name)).or_default().push(c);
68        }
69        Lookup { by_name, by_simple }
70    }
71}
72
73/// `net/minecraft/world/entity/LivingEntity` → `LivingEntity`; a nested
74/// class (`Outer$Inner`) by its own name (`Inner`).
75pub fn simple_name(internal: &str) -> &str {
76    let tail = internal.rsplit('/').next().unwrap_or(internal);
77    tail.rsplit(NESTED).next().unwrap_or(tail)
78}
79
80impl<'i> Lookup<'i> {
81    /// The class of internal name `name`.
82    pub fn class(&self, name: &str) -> Option<&'i Class> {
83        self.by_name.get(name).copied()
84    }
85
86    /// The classes a source's unqualified `name` may mean.
87    pub fn simple(&self, name: &str) -> &[&'i Class] {
88        self.by_simple
89            .get(name)
90            .map(Vec::as_slice)
91            .unwrap_or_default()
92    }
93
94    /// `class` and every class it extends, nearest first (the index's).
95    pub fn ancestry(&self, class: &'i Class) -> Vec<&'i Class> {
96        let mut out = vec![class];
97        while let Some(parent) = out
98            .last()
99            .and_then(|c| c.extends.as_deref())
100            .and_then(|p| self.class(p))
101        {
102            if out.iter().any(|c| c.name == parent.name) {
103                break;
104            }
105            out.push(parent);
106        }
107        out
108    }
109
110    /// The methods named `name` that `class` has, its own or inherited
111    /// (through what it extends and implements).
112    pub fn methods(&self, class: &'i Class, name: &str) -> Vec<&'i Member> {
113        let mut out = Vec::new();
114        let mut seen = Vec::new();
115        self.collect_methods(class, name, &mut out, &mut seen);
116        out
117    }
118
119    fn collect_methods(
120        &self,
121        class: &'i Class,
122        name: &str,
123        out: &mut Vec<&'i Member>,
124        seen: &mut Vec<&'i str>,
125    ) {
126        if seen.contains(&class.name.as_str()) {
127            return;
128        }
129        seen.push(&class.name);
130        for m in class.methods.iter().filter(|m| m.name == name) {
131            if !out.iter().any(|o| o.desc == m.desc) {
132                out.push(m);
133            }
134        }
135        let parents = class.extends.iter().chain(&class.implements);
136        for p in parents.filter_map(|p| self.class(p)) {
137            self.collect_methods(p, name, out, seen);
138        }
139    }
140
141    /// The field `name` of `class`, its own or inherited.
142    pub fn field(&self, class: &'i Class, name: &str) -> Option<&'i Member> {
143        self.ancestry(class)
144            .into_iter()
145            .find_map(|c| c.fields.iter().find(|f| f.name == name))
146    }
147}