Fork of daniellemaywood.uk/gleam — Wasm codegen work
25 kB
675 lines
1use std::{collections::HashMap, ops::Deref};
2
3use ecow::EcoString;
4use serde::Serialize;
5
6#[cfg(test)]
7mod tests;
8
9use crate::{
10 io::ordered_map,
11 type_::{
12 self, Deprecation, Type, TypeConstructor, TypeVar, ValueConstructorVariant,
13 expression::Implementations,
14 },
15};
16
17use crate::build::Package;
18
19/// The public interface of a package that gets serialised as a json object.
20#[derive(Serialize, Debug)]
21#[serde(rename_all = "kebab-case")]
22pub struct PackageInterface {
23 name: EcoString,
24 version: EcoString,
25 /// The Gleam version constraint that the package specifies in its `gleam.toml`.
26 gleam_version_constraint: Option<EcoString>,
27 /// A map from module name to its interface.
28 #[serde(serialize_with = "ordered_map")]
29 modules: HashMap<EcoString, ModuleInterface>,
30}
31
32#[derive(Serialize, Debug)]
33#[serde(rename_all = "kebab-case")]
34pub struct ModuleInterface {
35 /// A vector with the lines composing the module's documentation (that is
36 /// every line preceded by a `////`).
37 documentation: Vec<EcoString>,
38 /// A map from type alias name to its interface.
39 #[serde(serialize_with = "ordered_map")]
40 type_aliases: HashMap<EcoString, TypeAliasInterface>,
41 /// A map from type name to its interface.
42 #[serde(serialize_with = "ordered_map")]
43 types: HashMap<EcoString, TypeDefinitionInterface>,
44 /// A map from constant name to its interface.
45 #[serde(serialize_with = "ordered_map")]
46 constants: HashMap<EcoString, ConstantInterface>,
47 /// A map from function name to its interface.
48 #[serde(serialize_with = "ordered_map")]
49 functions: HashMap<EcoString, FunctionInterface>,
50}
51
52#[derive(Serialize, Debug)]
53#[serde(rename_all = "kebab-case")]
54pub struct TypeDefinitionInterface {
55 /// The definition's documentation comment (that is every line preceded by
56 /// `///`).
57 documentation: Option<EcoString>,
58 /// If the definition has a deprecation annotation `@deprecated("...")`
59 /// this field will hold the reason of the deprecation.
60 deprecation: Option<DeprecationInterface>,
61 /// The number of type variables in the type definition.
62 /// ```gleam
63 /// /// This type has 2 type variables.
64 /// type Result(a, b) {
65 /// Ok(a)
66 /// Error(b)
67 /// }
68 /// ```
69 parameters: usize,
70 /// A list of the type constructors. If the type is marked as opaque it
71 /// won't have any visible constructors.
72 constructors: Vec<TypeConstructorInterface>,
73}
74
75#[derive(Serialize, Debug)]
76#[serde(rename_all = "kebab-case")]
77pub struct TypeConstructorInterface {
78 /// The constructor's documentation comment (that is every line preceded by
79 /// `///`).
80 documentation: Option<EcoString>,
81 /// The name of the type constructor.
82 /// ```gleam
83 /// pub type Box(a) {
84 /// MyBox(value: a)
85 /// //^^^^^ This is the constructor's name
86 /// }
87 /// ```
88 name: EcoString,
89 /// A list of the parameters needed by the constructor.
90 /// ```gleam
91 /// pub type Box(a) {
92 /// MyBox(value: a)
93 /// // ^^^^^^^^ This is the constructor's parameter.
94 /// }
95 /// ```
96 parameters: Vec<ParameterInterface>,
97}
98
99#[derive(Serialize, Debug)]
100#[serde(rename_all = "kebab-case")]
101pub struct TypeAliasInterface {
102 /// The constructor's documentation comment (that is every line preceded by
103 /// `///`).
104 documentation: Option<EcoString>,
105 /// If the alias has a deprecation annotation `@deprecated("...")`
106 /// this field will hold the reason of the deprecation.
107 deprecation: Option<DeprecationInterface>,
108 /// The number of type variables in the type alias definition.
109 /// ```gleam
110 /// /// This type alias has 2 type variables.
111 /// type Results(a, b) = List(Restul(a, b))
112 /// ```
113 parameters: usize,
114 /// The aliased type.
115 /// ```gleam
116 /// type Ints = List(Int)
117 /// // ^^^^^^^^^ This is the aliased type in a type alias.
118 /// ```
119 alias: TypeInterface,
120}
121
122#[derive(Serialize, Debug)]
123#[serde(rename_all = "kebab-case")]
124pub struct ConstantInterface {
125 /// The constant's documentation comment (that is every line preceded by
126 /// `///`).
127 documentation: Option<EcoString>,
128 /// If the constant has a deprecation annotation `@deprecated("...")`
129 /// this field will hold the reason of the deprecation.
130 deprecation: Option<DeprecationInterface>,
131 implementations: ImplementationsInterface,
132 /// The constant's type.
133 #[serde(rename = "type")]
134 type_: TypeInterface,
135}
136
137/// A module's function. This differs from a simple `Fn` type as its arguments
138/// can be labelled.
139#[derive(Serialize, Debug)]
140#[serde(rename_all = "kebab-case")]
141pub struct FunctionInterface {
142 /// The function's documentation comment (that is every line preceded by
143 /// `///`).
144 documentation: Option<EcoString>,
145 /// If the constant has a deprecation annotation `@deprecated("...")`
146 /// this field will hold the reason of the deprecation.
147 deprecation: Option<DeprecationInterface>,
148 implementations: ImplementationsInterface,
149 parameters: Vec<ParameterInterface>,
150 #[serde(rename = "return")]
151 return_: TypeInterface,
152}
153
154/// Informations about how a value is implemented.
155#[derive(Debug, Serialize, Copy, Clone)]
156#[serde(rename_all = "kebab-case")]
157pub struct ImplementationsInterface {
158 /// Set to `true` if the const/function has a pure Gleam implementation
159 /// (that is, it never uses external code).
160 /// Being pure Gleam means that the function will support all Gleam
161 /// targets, even future ones that are not present to this day.
162 ///
163 /// Consider the following function:
164 ///
165 /// ```gleam
166 /// @external(erlang, "wibble", "wobble")
167 /// pub fn a_random_number() -> Int {
168 /// 4
169 /// // This is a default implementation.
170 /// }
171 /// ```
172 ///
173 /// The implementations for this function will look like this:
174 ///
175 /// ```json
176 /// {
177 /// gleam: true,
178 /// can_run_on_erlang: true,
179 /// can_run_on_javascript: true,
180 /// uses_erlang_externals: true,
181 /// uses_javascript_externals: false,
182 /// }
183 /// ```
184 ///
185 /// - `gleam: true` means that the function has a pure Gleam implementation
186 /// and thus it can be used on all Gleam targets with no problems.
187 /// - `can_run_on_erlang: false` the function can be called on the Erlang
188 /// target.
189 /// - `can_run_on_javascript: true` the function can be called on the JavaScript
190 /// target.
191 /// - `uses_erlang_externals: true` means that the function will use Erlang
192 /// external code when compiled to the Erlang target.
193 /// - `uses_javascript_externals: false` means that the function won't use
194 /// JavaScript external code when compiled to JavaScript. The function can
195 /// still be used on the JavaScript target since it has a pure Gleam
196 /// implementation.
197 gleam: bool,
198 /// Set to `true` if the const/function is defined using Erlang external
199 /// code. That means that the function will use Erlang code through FFI when
200 /// compiled for the Erlang target.
201 uses_erlang_externals: bool,
202 /// Set to `true` if the const/function is defined using JavaScript external
203 /// code. That means that the function will use JavaScript code through FFI
204 /// when compiled for the JavaScript target.
205 ///
206 /// Let's have a look at an example:
207 ///
208 /// ```gleam
209 /// @external(javascript, "wibble", "wobble")
210 /// pub fn javascript_only() -> Int
211 /// ```
212 ///
213 /// It's implementations field will look like this:
214 ///
215 /// ```json
216 /// {
217 /// gleam: false,
218 /// can_run_on_erlang: false,
219 /// can_run_on_javascript: true,
220 /// uses_erlang_externals: false,
221 /// uses_javascript_externals: true,
222 /// }
223 /// ```
224 ///
225 /// - `gleam: false` means that the function doesn't have a pure Gleam
226 /// implementations. This means that the function is only defined using
227 /// externals and can only be used on some targets.
228 /// - `can_run_on_erlang: false` the function cannot be called on the Erlang
229 /// target.
230 /// - `can_run_on_javascript: true` the function can be called on the JavaScript
231 /// target.
232 /// - `uses_erlang_externals: false` the function is not using external
233 /// Erlang code.
234 /// - `uses_javascript_externals: true` the function is using JavaScript
235 /// external code.
236 uses_javascript_externals: bool,
237 /// Whether the function can be called on the Erlang target, either due to a
238 /// pure Gleam implementation or an implementation that uses some Erlang
239 /// externals.
240 can_run_on_erlang: bool,
241 /// Whether the function can be called on the JavaScript target, either due
242 /// to a pure Gleam implementation or an implementation that uses some
243 /// JavaScript externals.
244 can_run_on_javascript: bool,
245}
246
247impl ImplementationsInterface {
248 fn from_implementations(implementations: &Implementations) -> ImplementationsInterface {
249 // It might look a bit silly to just recreate an identical structure with
250 // a different name. However, this way we won't inadvertently cause breaking
251 // changes if we were to change the names used by the `Implementations` struct
252 // that is used by the target tracking algorithm.
253 // By doing this we can change the target tracking and package interface
254 // separately!
255 //
256 // This pattern matching makes sure we will remember to handle any change
257 // in the `Implementations` struct.
258 let Implementations {
259 gleam,
260 uses_erlang_externals,
261 uses_javascript_externals,
262
263 can_run_on_erlang,
264 can_run_on_javascript,
265 } = implementations;
266
267 ImplementationsInterface {
268 gleam: *gleam,
269 uses_erlang_externals: *uses_erlang_externals,
270 uses_javascript_externals: *uses_javascript_externals,
271 can_run_on_erlang: *can_run_on_erlang,
272 can_run_on_javascript: *can_run_on_javascript,
273 }
274 }
275}
276
277#[derive(Serialize, Debug)]
278#[serde(rename_all = "kebab-case")]
279pub struct DeprecationInterface {
280 /// The reason for the deprecation.
281 message: EcoString,
282}
283
284impl DeprecationInterface {
285 fn from_deprecation(deprecation: &Deprecation) -> Option<DeprecationInterface> {
286 match deprecation {
287 Deprecation::NotDeprecated => None,
288 Deprecation::Deprecated { message } => Some(DeprecationInterface {
289 message: message.clone(),
290 }),
291 }
292 }
293}
294
295#[derive(Serialize, Debug)]
296#[serde(tag = "kind")]
297#[serde(rename_all = "kebab-case")]
298pub enum TypeInterface {
299 /// A tuple type like `#(Int, Float)`.
300 Tuple {
301 /// The types composing the tuple.
302 elements: Vec<TypeInterface>,
303 },
304 /// A function type like `fn(Int, String) -> String`.
305 Fn {
306 parameters: Vec<TypeInterface>,
307 #[serde(rename = "return")]
308 return_: Box<TypeInterface>,
309 },
310 /// A type variable.
311 /// ```gleam
312 /// pub fn wibble(value: a) -> a {}
313 /// // ^ This is a type variable.
314 /// ```
315 Variable { id: u64 },
316 /// A custom named type.
317 /// ```gleam
318 /// let value: Bool = True
319 /// ^^^^ This is a named type.
320 /// ```
321 ///
322 /// All prelude types - like Bool, String, etc. - are named types as well.
323 /// In that case their package is an empty string `""` and their module
324 /// name is the string `"gleam"`.
325 ///
326 Named {
327 name: EcoString,
328 /// The package the type is defined in.
329 package: EcoString,
330 /// The module the type is defined in.
331 module: EcoString,
332 /// The type parameters that might be needed to define a named type.
333 /// ```gleam
334 /// let result: Result(Int, e) = Ok(1)
335 /// // ^^^^^^ The `Result` named type has 2 parameters.
336 /// // In this case it's the Int type and a type
337 /// // variable.
338 /// ```
339 parameters: Vec<TypeInterface>,
340 },
341}
342
343#[derive(Serialize, Debug)]
344#[serde(rename_all = "kebab-case")]
345pub struct ParameterInterface {
346 /// If the parameter is labelled this will hold the label's name.
347 /// ```gleam
348 /// pub fn repeat(times n: Int) -> List(Int)
349 /// // ^^^^^ This is the parameter's label.
350 /// ```
351 label: Option<EcoString>,
352 /// The parameter's type.
353 /// ```gleam
354 /// pub fn repeat(times n: Int) -> List(Int)
355 /// // ^^^ This is the parameter's type.
356 /// ```
357 #[serde(rename = "type")]
358 type_: TypeInterface,
359}
360
361impl PackageInterface {
362 pub fn from_package(
363 package: &Package,
364 cached_modules: &im::HashMap<EcoString, type_::ModuleInterface>,
365 ) -> PackageInterface {
366 PackageInterface {
367 name: package.config.name.clone(),
368 version: package.config.version.to_string().into(),
369 gleam_version_constraint: package
370 .config
371 .gleam_version
372 .clone()
373 .map(|version| EcoString::from(version.to_string())),
374 modules: package
375 .modules
376 .iter()
377 .map(|module| &module.ast.type_info)
378 .chain(
379 package
380 .module_names
381 .iter()
382 .filter_map(|name| cached_modules.get(name)),
383 )
384 .filter(|module| !package.config.is_internal_module(module.name.as_str()))
385 .map(|module| (module.name.clone(), ModuleInterface::from_interface(module)))
386 .collect(),
387 }
388 }
389}
390
391impl ModuleInterface {
392 fn from_interface(interface: &type_::ModuleInterface) -> ModuleInterface {
393 let mut types = HashMap::new();
394 let mut type_aliases = HashMap::new();
395 let mut constants = HashMap::new();
396 let mut functions = HashMap::new();
397 for (name, constructor) in interface.types.iter().filter(|(name, c)| {
398 // Aliases are stored separately
399 c.publicity.is_public() && !interface.type_aliases.contains_key(*name)
400 }) {
401 let mut id_map = IdMap::new();
402
403 let TypeConstructor {
404 deprecation,
405 documentation,
406 ..
407 } = constructor;
408
409 for typed_parameter in &constructor.parameters {
410 id_map.add_type_variable_id(typed_parameter.as_ref());
411 }
412
413 let _ = types.insert(
414 name.clone(),
415 TypeDefinitionInterface {
416 documentation: documentation.clone(),
417 deprecation: DeprecationInterface::from_deprecation(&deprecation),
418 parameters: interface
419 .types
420 .get(&name.clone())
421 .map_or(vec![], |t| t.parameters.clone())
422 .len(),
423 constructors: if constructor.opaque {
424 vec![]
425 } else {
426 match interface.types_value_constructors.get(&name.clone()) {
427 Some(constructors) => constructors
428 .variants
429 .iter()
430 .map(|constructor| TypeConstructorInterface {
431 // TODO: Find documentation
432 documentation: None,
433 name: constructor.name.clone(),
434 parameters: constructor
435 .parameters
436 .iter()
437 .map(|arg| ParameterInterface {
438 label: arg.label.clone(),
439 // We share the same id_map between each step so that the
440 // incremental ids assigned are consisten with each other
441 type_: from_type_helper(
442 arg.type_.as_ref(),
443 &mut id_map,
444 ),
445 })
446 .collect(),
447 })
448 .collect(),
449 None => vec![],
450 }
451 },
452 },
453 );
454 }
455
456 for (name, alias) in interface
457 .type_aliases
458 .iter()
459 .filter(|(_, v)| v.publicity.is_public())
460 {
461 let _ = type_aliases.insert(
462 name.clone(),
463 TypeAliasInterface {
464 documentation: alias.documentation.clone(),
465 deprecation: DeprecationInterface::from_deprecation(&alias.deprecation),
466 parameters: alias.arity,
467 alias: TypeInterface::from_type(&alias.type_),
468 },
469 );
470 }
471
472 for (name, value) in interface
473 .values
474 .iter()
475 .filter(|(_, v)| v.publicity.is_public())
476 {
477 match (value.type_.as_ref(), value.variant.clone()) {
478 (
479 Type::Fn {
480 args: arguments,
481 retrn: return_type,
482 },
483 ValueConstructorVariant::ModuleFn {
484 documentation,
485 implementations,
486 ..
487 },
488 ) => {
489 let mut id_map = IdMap::new();
490
491 let _ = functions.insert(
492 name.clone(),
493 FunctionInterface {
494 implementations: ImplementationsInterface::from_implementations(
495 &implementations,
496 ),
497 deprecation: DeprecationInterface::from_deprecation(&value.deprecation),
498 documentation,
499 parameters: arguments
500 .iter()
501 .map(|arg| ParameterInterface {
502 // TODO: fixme
503 label: None,
504 type_: from_type_helper(arg, &mut id_map),
505 })
506 .collect(),
507 return_: from_type_helper(&return_type, &mut id_map),
508 },
509 );
510 }
511
512 (
513 type_,
514 ValueConstructorVariant::ModuleConstant {
515 documentation,
516 implementations,
517 ..
518 },
519 ) => {
520 let _ = constants.insert(
521 name.clone(),
522 ConstantInterface {
523 implementations: ImplementationsInterface::from_implementations(
524 &implementations,
525 ),
526 type_: TypeInterface::from_type(type_),
527 deprecation: DeprecationInterface::from_deprecation(&value.deprecation),
528 documentation,
529 },
530 );
531 }
532
533 _ => {}
534 }
535 }
536
537 ModuleInterface {
538 documentation: interface.documentation.clone(),
539 types,
540 type_aliases,
541 constants,
542 functions,
543 }
544 }
545}
546
547impl TypeInterface {
548 fn from_type(type_: &Type) -> TypeInterface {
549 from_type_helper(type_, &mut IdMap::new())
550 }
551}
552
553/// Turns a type into its interface, an `IdMap` is needed to make sure that all
554/// the type variables' ids that appear in the type are mapped to an incremental
555/// number and consistent with each other (that is, two types variables that
556/// have the same id will also have the same incremental number in the end).
557fn from_type_helper(type_: &Type, id_map: &mut IdMap) -> TypeInterface {
558 match type_ {
559 Type::Fn { args, retrn } => TypeInterface::Fn {
560 parameters: args
561 .iter()
562 .map(|arg| from_type_helper(arg.as_ref(), id_map))
563 .collect(),
564 return_: Box::new(from_type_helper(retrn, id_map)),
565 },
566
567 Type::Tuple { elems } => TypeInterface::Tuple {
568 elements: elems
569 .iter()
570 .map(|elem| from_type_helper(elem.as_ref(), id_map))
571 .collect(),
572 },
573
574 Type::Var { type_ } => match type_
575 .as_ref()
576 .try_borrow()
577 .expect("borrow type after inference")
578 .deref()
579 {
580 TypeVar::Link { type_ } => from_type_helper(type_, id_map),
581 // Since package serialisation happens after inference there
582 // should be no unbound type variables.
583 // TODO: This branch should be `unreachable!()` but because of
584 // https://github.com/gleam-lang/gleam/issues/2533
585 // we sometimes end up with those in top level
586 // definitions.
587 // However, `Unbound` and `Generic` ids are generated
588 // using the same generator so we have no problem treating
589 // unbound variables as generic ones since ids will never
590 // overlap.
591 // Once #2533 is closed this branch can be turned back to
592 // be unreachable!().
593 TypeVar::Unbound { id } | TypeVar::Generic { id } => TypeInterface::Variable {
594 id: id_map.map_id(*id),
595 },
596 },
597
598 Type::Named {
599 name,
600 module,
601 args,
602 package,
603 ..
604 } => TypeInterface::Named {
605 name: name.clone(),
606 package: package.clone(),
607 module: module.clone(),
608 parameters: args
609 .iter()
610 .map(|arg| from_type_helper(arg.as_ref(), id_map))
611 .collect(),
612 },
613 }
614}
615
616/// This is a map that is used to map type variable id's to progressive numbers
617/// starting from 0.
618/// After type inference the ids associated with type variables can be quite
619/// high and are not the best to produce a human/machine readable output.
620///
621/// Imagine a function like this one: `pub fn wibble(item: a, rest: b) -> c`
622/// What we want here is for type variables to have increasing ids starting from
623/// 0: `a` with id `0`, `b` with id `1` and `c` with id `2`.
624///
625/// This map allows us to keep track of the ids we've run into and map those to
626/// their incremental counterpart starting from 0.
627struct IdMap {
628 next_id: u64,
629 ids: HashMap<u64, u64>,
630}
631
632impl IdMap {
633 /// Create a new map that will assign id numbers starting from 0.
634 fn new() -> IdMap {
635 IdMap {
636 next_id: 0,
637 ids: HashMap::new(),
638 }
639 }
640
641 /// Map an id to its mapped counterpart starting from 0. If an id has never
642 /// been seen before it will be assigned a new incremental number.
643 fn map_id(&mut self, id: u64) -> u64 {
644 match self.ids.get(&id) {
645 Some(mapped_id) => *mapped_id,
646 None => {
647 let mapped_id = self.next_id;
648 let _ = self.ids.insert(id, mapped_id);
649 self.next_id += 1;
650 mapped_id
651 }
652 }
653 }
654
655 /// If the type is a type variable, and has not been seen before, it will
656 /// be assigned to a new incremental number.
657 fn add_type_variable_id(&mut self, type_: &Type) {
658 match type_ {
659 // These types have no id to add to the map.
660 Type::Named { .. } | Type::Fn { .. } | Type::Tuple { .. } => (),
661 // If the type is actually a type variable whose id needs to be mapped.
662 Type::Var { type_ } => match type_
663 .as_ref()
664 .try_borrow()
665 .expect("borrow type after inference")
666 .deref()
667 {
668 TypeVar::Link { .. } => (),
669 TypeVar::Unbound { id } | TypeVar::Generic { id } => {
670 let _ = self.map_id(*id);
671 }
672 },
673 }
674 }
675}