Skip to content

Commit 98aba0b

Browse files
authored
refactor: decompose typegen/typescript module (#286)
1 parent 36a00dc commit 98aba0b

8 files changed

Lines changed: 948 additions & 897 deletions

File tree

crates/plotnik-lib/src/typegen/typescript.rs

Lines changed: 0 additions & 897 deletions
This file was deleted.
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
//! Type graph traversal and analysis.
2+
3+
use std::collections::{HashMap, HashSet};
4+
5+
use crate::bytecode::{QTypeId, TypeKind};
6+
7+
use super::Emitter;
8+
9+
impl Emitter<'_> {
10+
pub(super) fn collect_builtin_references(&mut self) {
11+
for i in 0..self.entrypoints.len() {
12+
let ep = self.entrypoints.get(i);
13+
self.collect_refs_recursive(ep.result_type);
14+
}
15+
}
16+
17+
fn collect_refs_recursive(&mut self, type_id: QTypeId) {
18+
// Cycle detection
19+
if !self.refs_visited.insert(type_id) {
20+
return;
21+
}
22+
23+
let Some(type_def) = self.types.get(type_id) else {
24+
return;
25+
};
26+
27+
let Some(kind) = type_def.type_kind() else {
28+
return;
29+
};
30+
31+
match kind {
32+
TypeKind::Node => {
33+
self.node_referenced = true;
34+
}
35+
TypeKind::String | TypeKind::Void => {
36+
// No action needed for primitives
37+
}
38+
TypeKind::Struct | TypeKind::Enum => {
39+
let member_types: Vec<_> = self
40+
.types
41+
.members_of(&type_def)
42+
.map(|m| m.type_id)
43+
.collect();
44+
for ty in member_types {
45+
self.collect_refs_recursive(ty);
46+
}
47+
}
48+
TypeKind::ArrayZeroOrMore | TypeKind::ArrayOneOrMore | TypeKind::Optional => {
49+
self.collect_refs_recursive(QTypeId(type_def.data));
50+
}
51+
TypeKind::Alias => {
52+
// Alias to Node
53+
self.node_referenced = true;
54+
}
55+
}
56+
}
57+
58+
pub(super) fn sort_topologically(&self, types: HashSet<QTypeId>) -> Vec<QTypeId> {
59+
let mut deps: HashMap<QTypeId, HashSet<QTypeId>> = HashMap::new();
60+
let mut rdeps: HashMap<QTypeId, HashSet<QTypeId>> = HashMap::new();
61+
62+
for &tid in &types {
63+
deps.entry(tid).or_default();
64+
rdeps.entry(tid).or_default();
65+
}
66+
67+
// Build dependency graph
68+
for &tid in &types {
69+
for dep in self.get_direct_deps(tid) {
70+
if types.contains(&dep) && dep != tid {
71+
deps.entry(tid).or_default().insert(dep);
72+
rdeps.entry(dep).or_default().insert(tid);
73+
}
74+
}
75+
}
76+
77+
// Kahn's algorithm
78+
let mut result = Vec::with_capacity(types.len());
79+
let mut queue: Vec<QTypeId> = deps
80+
.iter()
81+
.filter(|(_, d)| d.is_empty())
82+
.map(|(&tid, _)| tid)
83+
.collect();
84+
85+
queue.sort_by_key(|tid| tid.0);
86+
87+
while let Some(tid) = queue.pop() {
88+
result.push(tid);
89+
if let Some(dependents) = rdeps.get(&tid) {
90+
for &dependent in dependents {
91+
if let Some(dep_set) = deps.get_mut(&dependent) {
92+
dep_set.remove(&tid);
93+
if dep_set.is_empty() {
94+
queue.push(dependent);
95+
queue.sort_by_key(|t| t.0);
96+
}
97+
}
98+
}
99+
}
100+
}
101+
102+
result
103+
}
104+
105+
pub(super) fn collect_reachable_types(&self, type_id: QTypeId, out: &mut HashSet<QTypeId>) {
106+
if out.contains(&type_id) {
107+
return;
108+
}
109+
110+
let Some(type_def) = self.types.get(type_id) else {
111+
return;
112+
};
113+
114+
let Some(kind) = type_def.type_kind() else {
115+
return;
116+
};
117+
118+
match kind {
119+
TypeKind::Void | TypeKind::Node | TypeKind::String => {}
120+
TypeKind::Struct => {
121+
out.insert(type_id);
122+
for member in self.types.members_of(&type_def) {
123+
self.collect_reachable_types(member.type_id, out);
124+
}
125+
}
126+
TypeKind::Enum => {
127+
out.insert(type_id);
128+
for member in self.types.members_of(&type_def) {
129+
self.collect_enum_variant_refs(member.type_id, out);
130+
}
131+
}
132+
TypeKind::Alias => {
133+
out.insert(type_id);
134+
}
135+
TypeKind::ArrayZeroOrMore | TypeKind::ArrayOneOrMore | TypeKind::Optional => {
136+
self.collect_reachable_types(QTypeId(type_def.data), out);
137+
}
138+
}
139+
}
140+
141+
/// Collect reachable types from enum variant payloads.
142+
/// Recurses into struct fields but doesn't add the payload struct itself.
143+
fn collect_enum_variant_refs(&self, type_id: QTypeId, out: &mut HashSet<QTypeId>) {
144+
let Some(type_def) = self.types.get(type_id) else {
145+
return;
146+
};
147+
148+
// For struct payloads, don't add the struct itself (it will be inlined),
149+
// but recurse into its fields to find named types.
150+
if type_def.type_kind() == Some(TypeKind::Struct) {
151+
for member in self.types.members_of(&type_def) {
152+
self.collect_reachable_types(member.type_id, out);
153+
}
154+
} else {
155+
// For non-struct payloads, fall back to regular collection.
156+
self.collect_reachable_types(type_id, out);
157+
}
158+
}
159+
160+
pub(super) fn get_direct_deps(&self, type_id: QTypeId) -> Vec<QTypeId> {
161+
let Some(type_def) = self.types.get(type_id) else {
162+
return vec![];
163+
};
164+
165+
let Some(kind) = type_def.type_kind() else {
166+
return vec![];
167+
};
168+
169+
match kind {
170+
TypeKind::Void | TypeKind::Node | TypeKind::String | TypeKind::Alias => vec![],
171+
TypeKind::Struct | TypeKind::Enum => self
172+
.types
173+
.members_of(&type_def)
174+
.flat_map(|member| self.unwrap_for_deps(member.type_id))
175+
.collect(),
176+
TypeKind::ArrayZeroOrMore | TypeKind::ArrayOneOrMore | TypeKind::Optional => {
177+
self.unwrap_for_deps(QTypeId(type_def.data))
178+
}
179+
}
180+
}
181+
182+
fn unwrap_for_deps(&self, type_id: QTypeId) -> Vec<QTypeId> {
183+
let Some(type_def) = self.types.get(type_id) else {
184+
return vec![];
185+
};
186+
187+
let Some(kind) = type_def.type_kind() else {
188+
return vec![];
189+
};
190+
191+
match kind {
192+
TypeKind::Void | TypeKind::Node | TypeKind::String => vec![],
193+
TypeKind::ArrayZeroOrMore | TypeKind::ArrayOneOrMore | TypeKind::Optional => {
194+
self.unwrap_for_deps(QTypeId(type_def.data))
195+
}
196+
TypeKind::Struct | TypeKind::Enum | TypeKind::Alias => vec![type_id],
197+
}
198+
}
199+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//! Configuration types for TypeScript emission.
2+
3+
use crate::Colors;
4+
5+
/// How to represent the void type in TypeScript.
6+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7+
pub enum VoidType {
8+
/// `undefined` - the absence of a value
9+
#[default]
10+
Undefined,
11+
/// `null` - explicit null value
12+
Null,
13+
}
14+
15+
/// Configuration for TypeScript emission.
16+
#[derive(Clone, Debug)]
17+
pub struct Config {
18+
/// Whether to export types
19+
pub export: bool,
20+
/// Whether to emit the Node type definition
21+
pub emit_node_type: bool,
22+
/// Use verbose node representation (with kind, text, etc.)
23+
pub verbose_nodes: bool,
24+
/// How to represent the void type
25+
pub void_type: VoidType,
26+
/// Color configuration for output
27+
pub colors: Colors,
28+
}
29+
30+
impl Default for Config {
31+
fn default() -> Self {
32+
Self {
33+
export: true,
34+
emit_node_type: true,
35+
verbose_nodes: false,
36+
void_type: VoidType::default(),
37+
colors: Colors::OFF,
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)