Fork of daniellemaywood.uk/gleam — Wasm codegen work
25 kB
681 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 /// Whether the function can be called on the Erlang target, either due to a
241 /// pure Gleam implementation or an implementation that uses some Erlang
242 /// externals.
243 can_run_on_erlang: bool,
244 /// Whether the function can be called on the JavaScript target, either due
245 /// to a pure Gleam implementation or an implementation that uses some
246 /// JavaScript externals.
247 can_run_on_javascript: bool,
248}
249
250impl ImplementationsInterface {
251 fn from_implementations(implementations: &Implementations) -> ImplementationsInterface {
252 // It might look a bit silly to just recreate an identical structure with
253 // a different name. However, this way we won't inadvertently cause breaking
254 // changes if we were to change the names used by the `Implementations` struct
255 // that is used by the target tracking algorithm.
256 // By doing this we can change the target tracking and package interface
257 // separately!
258 //
259 // This pattern matching makes sure we will remember to handle any change
260 // in the `Implementations` struct.
261 let Implementations {
262 gleam,
263 uses_erlang_externals,
264 uses_javascript_externals,
265
266 can_run_on_erlang,
267 can_run_on_javascript,
268 } = implementations;
269
270 ImplementationsInterface {
271 gleam: *gleam,
272 uses_erlang_externals: *uses_erlang_externals,
273 uses_javascript_externals: *uses_javascript_externals,
274 can_run_on_erlang: *can_run_on_erlang,
275 can_run_on_javascript: *can_run_on_javascript,
276 }
277 }
278}
279
280#[derive(Serialize, Debug)]
281#[serde(rename_all = "kebab-case")]
282pub struct DeprecationInterface {
283 /// The reason for the deprecation.
284 message: EcoString,
285}
286
287impl DeprecationInterface {
288 fn from_deprecation(deprecation: &Deprecation) -> Option<DeprecationInterface> {
289 match deprecation {
290 Deprecation::NotDeprecated => None,
291 Deprecation::Deprecated { message } => Some(DeprecationInterface {
292 message: message.clone(),
293 }),
294 }
295 }
296}
297
298#[derive(Serialize, Debug)]
299#[serde(tag = "kind")]
300#[serde(rename_all = "kebab-case")]
301pub enum TypeInterface {
302 /// A tuple type like `#(Int, Float)`.
303 Tuple {
304 /// The types composing the tuple.
305 elements: Vec<TypeInterface>,
306 },
307 /// A function type like `fn(Int, String) -> String`.
308 Fn {
309 parameters: Vec<TypeInterface>,
310 #[serde(rename = "return")]
311 return_: Box<TypeInterface>,
312 },
313 /// A type variable.
314 /// ```gleam
315 /// pub fn wibble(value: a) -> a {}
316 /// // ^ This is a type variable.
317 /// ```
318 Variable { id: u64 },
319 /// A custom named type.
320 /// ```gleam
321 /// let value: Bool = True
322 /// ^^^^ This is a named type.
323 /// ```
324 ///
325 /// All prelude types - like Bool, String, etc. - are named types as well.
326 /// In that case their package is an empty string `""` and their module
327 /// name is the string `"gleam"`.
328 ///
329 Named {
330 name: EcoString,
331 /// The package the type is defined in.
332 package: EcoString,
333 /// The module the type is defined in.
334 module: EcoString,
335 /// The type parameters that might be needed to define a named type.
336 /// ```gleam
337 /// let result: Result(Int, e) = Ok(1)
338 /// // ^^^^^^ The `Result` named type has 2 parameters.
339 /// // In this case it's the Int type and a type
340 /// // variable.
341 /// ```
342 parameters: Vec<TypeInterface>,
343 },
344}
345
346#[derive(Serialize, Debug)]
347#[serde(rename_all = "kebab-case")]
348pub struct ParameterInterface {
349 /// If the parameter is labelled this will hold the label's name.
350 /// ```gleam
351 /// pub fn repeat(times n: Int) -> List(Int)
352 /// // ^^^^^ This is the parameter's label.
353 /// ```
354 label: Option<EcoString>,
355 /// The parameter's type.
356 /// ```gleam
357 /// pub fn repeat(times n: Int) -> List(Int)
358 /// // ^^^ This is the parameter's type.
359 /// ```
360 #[serde(rename = "type")]
361 type_: TypeInterface,
362}
363
364impl PackageInterface {
365 pub fn from_package(
366 package: &Package,
367 cached_modules: &im::HashMap<EcoString, type_::ModuleInterface>,
368 ) -> PackageInterface {
369 PackageInterface {
370 name: package.config.name.clone(),
371 version: package.config.version.to_string().into(),
372 gleam_version_constraint: package
373 .config
374 .gleam_version
375 .clone()
376 .map(|version| EcoString::from(version.hex().to_string())),
377 modules: package
378 .modules
379 .iter()
380 .map(|module| &module.ast.type_info)
381 .chain(
382 package
383 .cached_module_names
384 .iter()
385 .filter_map(|name| cached_modules.get(name)),
386 )
387 .filter(|module| !package.config.is_internal_module(module.name.as_str()))
388 .map(|module| (module.name.clone(), ModuleInterface::from_interface(module)))
389 .collect(),
390 }
391 }
392}
393
394impl ModuleInterface {
395 fn from_interface(interface: &type_::ModuleInterface) -> ModuleInterface {
396 let mut types = HashMap::new();
397 let mut type_aliases = HashMap::new();
398 let mut constants = HashMap::new();
399 let mut functions = HashMap::new();
400 for (name, constructor) in interface.types.iter().filter(|(name, c)| {
401 // Aliases are stored separately
402 c.publicity.is_public() && !interface.type_aliases.contains_key(*name)
403 }) {
404 let mut id_map = IdMap::new();
405
406 let TypeConstructor {
407 deprecation,
408 documentation,
409 ..
410 } = constructor;
411
412 for typed_parameter in &constructor.parameters {
413 id_map.add_type_variable_id(typed_parameter.as_ref());
414 }
415
416 let _ = types.insert(
417 name.clone(),
418 TypeDefinitionInterface {
419 documentation: documentation.clone(),
420 deprecation: DeprecationInterface::from_deprecation(deprecation),
421 parameters: interface
422 .types
423 .get(&name.clone())
424 .map_or(vec![], |t| t.parameters.clone())
425 .len(),
426 constructors: match interface.types_value_constructors.get(&name.clone()) {
427 Some(TypeVariantConstructors {
428 variants,
429 opaque: Opaque::NotOpaque,
430 ..
431 }) => variants
432 .iter()
433 .map(|constructor| TypeConstructorInterface {
434 documentation: constructor.documentation.clone(),
435 name: constructor.name.clone(),
436 parameters: constructor
437 .parameters
438 .iter()
439 .map(|arg| ParameterInterface {
440 label: arg.label.clone(),
441 // We share the same id_map between each step so that the
442 // incremental ids assigned are consisten with each other
443 type_: from_type_helper(arg.type_.as_ref(), &mut id_map),
444 })
445 .collect(),
446 })
447 .collect(),
448 Some(_) | None => Vec::new(),
449 },
450 },
451 );
452 }
453
454 for (name, alias) in interface
455 .type_aliases
456 .iter()
457 .filter(|(_, v)| v.publicity.is_public())
458 {
459 let _ = type_aliases.insert(
460 name.clone(),
461 TypeAliasInterface {
462 documentation: alias.documentation.clone(),
463 deprecation: DeprecationInterface::from_deprecation(&alias.deprecation),
464 parameters: alias.arity,
465 alias: TypeInterface::from_type(&alias.type_),
466 },
467 );
468 }
469
470 for (name, value) in interface
471 .values
472 .iter()
473 .filter(|(_, v)| v.publicity.is_public())
474 {
475 match (value.type_.as_ref(), value.variant.clone()) {
476 (
477 Type::Fn {
478 arguments,
479 return_: return_type,
480 },
481 ValueConstructorVariant::ModuleFn {
482 documentation,
483 implementations,
484 field_map,
485 ..
486 },
487 ) => {
488 let mut id_map = IdMap::new();
489
490 let reverse_field_map = field_map
491 .as_ref()
492 .map(|field_map| field_map.indices_to_labels())
493 .unwrap_or_default();
494
495 let _ = functions.insert(
496 name.clone(),
497 FunctionInterface {
498 implementations: ImplementationsInterface::from_implementations(
499 &implementations,
500 ),
501 deprecation: DeprecationInterface::from_deprecation(&value.deprecation),
502 documentation,
503 parameters: arguments
504 .iter()
505 .enumerate()
506 .map(|(index, type_)| ParameterInterface {
507 label: reverse_field_map
508 .get(&(index as u32))
509 .map(|label| (*label).clone()),
510 type_: from_type_helper(type_, &mut id_map),
511 })
512 .collect(),
513 return_: from_type_helper(return_type, &mut id_map),
514 },
515 );
516 }
517
518 (
519 type_,
520 ValueConstructorVariant::ModuleConstant {
521 documentation,
522 implementations,
523 ..
524 },
525 ) => {
526 let _ = constants.insert(
527 name.clone(),
528 ConstantInterface {
529 implementations: ImplementationsInterface::from_implementations(
530 &implementations,
531 ),
532 type_: TypeInterface::from_type(type_),
533 deprecation: DeprecationInterface::from_deprecation(&value.deprecation),
534 documentation,
535 },
536 );
537 }
538
539 _ => {}
540 }
541 }
542
543 ModuleInterface {
544 documentation: interface.documentation.clone(),
545 types,
546 type_aliases,
547 constants,
548 functions,
549 }
550 }
551}
552
553impl TypeInterface {
554 fn from_type(type_: &Type) -> TypeInterface {
555 from_type_helper(type_, &mut IdMap::new())
556 }
557}
558
559/// Turns a type into its interface, an `IdMap` is needed to make sure that all
560/// the type variables' ids that appear in the type are mapped to an incremental
561/// number and consistent with each other (that is, two types variables that
562/// have the same id will also have the same incremental number in the end).
563fn from_type_helper(type_: &Type, id_map: &mut IdMap) -> TypeInterface {
564 match type_ {
565 Type::Fn { arguments, return_ } => TypeInterface::Fn {
566 parameters: arguments
567 .iter()
568 .map(|argument| from_type_helper(argument.as_ref(), id_map))
569 .collect(),
570 return_: Box::new(from_type_helper(return_, id_map)),
571 },
572
573 Type::Tuple { elements } => TypeInterface::Tuple {
574 elements: elements
575 .iter()
576 .map(|element| from_type_helper(element.as_ref(), id_map))
577 .collect(),
578 },
579
580 Type::Var { type_ } => match type_
581 .as_ref()
582 .try_borrow()
583 .expect("borrow type after inference")
584 .deref()
585 {
586 TypeVar::Link { type_ } => from_type_helper(type_, id_map),
587 // Since package serialisation happens after inference there
588 // should be no unbound type variables.
589 // TODO: This branch should be `unreachable!()` but because of
590 // https://github.com/gleam-lang/gleam/issues/2533
591 // we sometimes end up with those in top level
592 // definitions.
593 // However, `Unbound` and `Generic` ids are generated
594 // using the same generator so we have no problem treating
595 // unbound variables as generic ones since ids will never
596 // overlap.
597 // Once #2533 is closed this branch can be turned back to
598 // be unreachable!().
599 TypeVar::Unbound { id } | TypeVar::Generic { id } => TypeInterface::Variable {
600 id: id_map.map_id(*id),
601 },
602 },
603
604 Type::Named {
605 name,
606 module,
607 arguments,
608 package,
609 ..
610 } => TypeInterface::Named {
611 name: name.clone(),
612 package: package.clone(),
613 module: module.clone(),
614 parameters: arguments
615 .iter()
616 .map(|argument| from_type_helper(argument.as_ref(), id_map))
617 .collect(),
618 },
619 }
620}
621
622/// This is a map that is used to map type variable id's to progressive numbers
623/// starting from 0.
624/// After type inference the ids associated with type variables can be quite
625/// high and are not the best to produce a human/machine readable output.
626///
627/// Imagine a function like this one: `pub fn wibble(item: a, rest: b) -> c`
628/// What we want here is for type variables to have increasing ids starting from
629/// 0: `a` with id `0`, `b` with id `1` and `c` with id `2`.
630///
631/// This map allows us to keep track of the ids we've run into and map those to
632/// their incremental counterpart starting from 0.
633struct IdMap {
634 next_id: u64,
635 ids: HashMap<u64, u64>,
636}
637
638impl IdMap {
639 /// Create a new map that will assign id numbers starting from 0.
640 fn new() -> IdMap {
641 IdMap {
642 next_id: 0,
643 ids: HashMap::new(),
644 }
645 }
646
647 /// Map an id to its mapped counterpart starting from 0. If an id has never
648 /// been seen before it will be assigned a new incremental number.
649 fn map_id(&mut self, id: u64) -> u64 {
650 match self.ids.get(&id) {
651 Some(mapped_id) => *mapped_id,
652 None => {
653 let mapped_id = self.next_id;
654 let _ = self.ids.insert(id, mapped_id);
655 self.next_id += 1;
656 mapped_id
657 }
658 }
659 }
660
661 /// If the type is a type variable, and has not been seen before, it will
662 /// be assigned to a new incremental number.
663 fn add_type_variable_id(&mut self, type_: &Type) {
664 match type_ {
665 // These types have no id to add to the map.
666 Type::Named { .. } | Type::Fn { .. } | Type::Tuple { .. } => (),
667 // If the type is actually a type variable whose id needs to be mapped.
668 Type::Var { type_ } => match type_
669 .as_ref()
670 .try_borrow()
671 .expect("borrow type after inference")
672 .deref()
673 {
674 TypeVar::Link { .. } => (),
675 TypeVar::Unbound { id } | TypeVar::Generic { id } => {
676 let _ = self.map_id(*id);
677 }
678 },
679 }
680 }
681}