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