Skip to main content

lisette_syntax/program/
emit_input.rs

1use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
2
3use ecow::EcoString;
4
5use crate::ast::{BindingId as AstBindingId, Pattern, RestPattern, Span};
6use crate::types::Type;
7
8use super::{Definition, File, ModuleInfo};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11struct ReceiverId(Span);
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum ReceiverCoercion {
15    /// Insert `&` to convert `T` to `Ref<T>`
16    AutoAddress,
17    /// Insert `*` to convert `Ref<T>` to `T`
18    AutoDeref,
19}
20
21#[derive(Debug, Clone, Default)]
22pub struct CoercionInfo {
23    receivers: HashMap<ReceiverId, ReceiverCoercion>,
24}
25
26impl CoercionInfo {
27    pub fn mark_coercion(&mut self, span: Span, coercion: ReceiverCoercion) {
28        self.receivers.insert(ReceiverId(span), coercion);
29    }
30
31    pub fn get_coercion(&self, span: Span) -> Option<ReceiverCoercion> {
32        self.receivers.get(&ReceiverId(span)).copied()
33    }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37struct BindingId(Span);
38
39#[derive(Debug, Clone, Default)]
40pub struct UnusedInfo {
41    bindings: HashSet<BindingId>,
42    definitions: HashSet<BindingId>,
43    pub imports_by_module: HashMap<EcoString, HashSet<EcoString>>,
44}
45
46impl UnusedInfo {
47    pub fn mark_binding_unused(&mut self, span: Span) {
48        self.bindings.insert(BindingId(span));
49    }
50
51    pub fn is_unused_binding(&self, pattern: &Pattern) -> bool {
52        match pattern {
53            Pattern::Identifier { span, .. } => self.bindings.contains(&BindingId(*span)),
54            _ => false,
55        }
56    }
57
58    pub fn is_unused_rest_binding(&self, rest: &RestPattern) -> bool {
59        match rest {
60            RestPattern::Bind { span, .. } => self.bindings.contains(&BindingId(*span)),
61            _ => false,
62        }
63    }
64
65    pub fn mark_definition_unused(&mut self, span: Span) {
66        self.definitions.insert(BindingId(span));
67    }
68
69    pub fn is_unused_definition(&self, span: &Span) -> bool {
70        self.definitions.contains(&BindingId(*span))
71    }
72}
73
74#[derive(Debug, Clone, Default)]
75pub struct MutationInfo {
76    bindings: HashSet<AstBindingId>,
77}
78
79impl MutationInfo {
80    pub fn mark_binding_mutated(&mut self, id: AstBindingId) {
81        self.bindings.insert(id);
82    }
83
84    pub fn is_mutated(&self, id: AstBindingId) -> bool {
85        self.bindings.contains(&id)
86    }
87}
88
89/// What a dot access resolved to during type checking.
90///
91/// Pre-computed in semantics to avoid re-derivation in the emitter.
92/// The emitter can use this to skip cascading `try_classify_*` lookups.
93/// `is_exported` indicates whether the Go name should be capitalized.
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum DotAccessKind {
96    /// Named struct field access
97    StructField { is_exported: bool },
98    /// Tuple struct field access (e.g., `point.0` on `struct Point(int, int)`).
99    /// `is_newtype` is true when the struct has exactly 1 field and no generics,
100    /// meaning access should emit a type cast rather than `.F0`.
101    TupleStructField { is_newtype: bool },
102    /// Tuple element access (e.g., `t.0`, `t.1`)
103    TupleElement,
104    /// Module member access (e.g., `mod.func`)
105    ModuleMember,
106    /// Value enum variant (Go constant, e.g., `reflect.String`)
107    ValueEnumVariant,
108    /// ADT enum variant constructor (e.g., `makeColorRed[T]()`)
109    EnumVariant,
110    /// Instance method (has `self` receiver)
111    InstanceMethod { is_exported: bool },
112    /// Instance method used as a first-class value (not called).
113    /// E.g., `Point.area` used as a callback. The emitter needs to know
114    /// whether the receiver is a pointer to emit Go method expression syntax.
115    InstanceMethodValue {
116        is_exported: bool,
117        is_pointer_receiver: bool,
118    },
119    /// Static method (no `self` receiver)
120    StaticMethod { is_exported: bool },
121}
122
123/// What kind of native built-in type (Slice, Map, Channel, etc.) a call targets.
124///
125/// Defined in `syntax` so that semantics can classify calls without depending on
126/// emit-specific types. The emitter maps this to its internal `NativeGoType`.
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum NativeTypeKind {
129    Slice,
130    EnumeratedSlice,
131    Map,
132    Channel,
133    Sender,
134    Receiver,
135    String,
136}
137
138impl NativeTypeKind {
139    pub fn from_type(ty: &Type) -> Option<Self> {
140        let resolved = ty.resolve().strip_refs();
141        if let Type::Constructor { ref id, .. } = resolved
142            && (id.starts_with("@import/go:") || id.starts_with("go:"))
143        {
144            return None;
145        }
146        let name = resolved.get_name()?;
147        Self::from_name(name)
148    }
149
150    pub fn from_name(name: &str) -> Option<Self> {
151        match name {
152            "Slice" => Some(Self::Slice),
153            "EnumeratedSlice" => Some(Self::EnumeratedSlice),
154            "Map" => Some(Self::Map),
155            "Channel" => Some(Self::Channel),
156            "Sender" => Some(Self::Sender),
157            "Receiver" => Some(Self::Receiver),
158            "string" => Some(Self::String),
159            _ => None,
160        }
161    }
162}
163
164/// What a call expression resolved to during type checking.
165///
166/// Pre-computed in semantics to avoid re-derivation in the emitter's call dispatch.
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum CallKind {
169    /// Regular function or method call
170    Regular,
171    /// Tuple struct constructor (e.g., `Point(1, 2)`)
172    TupleStructConstructor,
173    /// Type assertion (`assert_type`)
174    AssertType,
175    /// UFCS method call: `receiver.method()` where method is a free function
176    UfcsMethod,
177    /// Native type constructor (e.g., `Channel.new`, `Map.new`, `Slice.new`)
178    NativeConstructor(NativeTypeKind),
179    /// Native type instance method via dot access (e.g., `slice.append(x)`)
180    NativeMethod(NativeTypeKind),
181    /// Native type method via identifier (e.g., `Slice.contains(s, x)`)
182    NativeMethodIdentifier(NativeTypeKind),
183    /// Receiver method in UFCS syntax: `Type.method(receiver, args)`
184    ReceiverMethodUfcs { is_public: bool },
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
188struct ResolutionId(Span);
189
190/// Pre-computed resolution metadata from type checking.
191///
192/// Follows the same pattern as `CoercionInfo`: keyed by expression span,
193/// populated during inference, consumed by the emitter.
194#[derive(Debug, Clone, Default)]
195pub struct ResolutionInfo {
196    dot_accesses: HashMap<ResolutionId, DotAccessKind>,
197    calls: HashMap<ResolutionId, CallKind>,
198}
199
200impl ResolutionInfo {
201    pub fn mark_dot_access(&mut self, span: Span, kind: DotAccessKind) {
202        self.dot_accesses.insert(ResolutionId(span), kind);
203    }
204
205    pub fn get_dot_access(&self, span: Span) -> Option<DotAccessKind> {
206        self.dot_accesses.get(&ResolutionId(span)).copied()
207    }
208
209    pub fn mark_call(&mut self, span: Span, meta: CallKind) {
210        self.calls.insert(ResolutionId(span), meta);
211    }
212
213    pub fn get_call(&self, span: Span) -> Option<CallKind> {
214        self.calls.get(&ResolutionId(span)).copied()
215    }
216}
217
218pub struct EmitInput {
219    pub files: HashMap<u32, File>,
220    pub definitions: HashMap<EcoString, Definition>,
221    pub modules: HashMap<String, ModuleInfo>,
222    pub entry_module_id: String,
223    pub unused: UnusedInfo,
224    pub mutations: MutationInfo,
225    pub coercions: CoercionInfo,
226    pub resolutions: ResolutionInfo,
227    pub cached_modules: HashSet<String>,
228    pub ufcs_methods: HashSet<(String, String)>,
229}