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