Fork of daniellemaywood.uk/gleam — Wasm codegen work
25 kB
676 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 pub 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
398 .types
399 .iter()
400 .filter(|(_, c)| c.publicity.is_public())
401 {
402 let mut id_map = IdMap::new();
403
404 let TypeConstructor {
405 deprecation,
406 documentation,
407 ..
408 } = constructor;
409
410 for typed_parameter in &constructor.parameters {
411 id_map.add_type_variable_id(typed_parameter.as_ref());
412 }
413
414 let _ = types.insert(
415 name.clone(),
416 TypeDefinitionInterface {
417 documentation: documentation.clone(),
418 deprecation: DeprecationInterface::from_deprecation(&deprecation),
419 parameters: interface
420 .types
421 .get(&name.clone())
422 .map_or(vec![], |t| t.parameters.clone())
423 .len(),
424 constructors: if constructor.opaque {
425 vec![]
426 } else {
427 match interface.types_value_constructors.get(&name.clone()) {
428 Some(constructors) => constructors
429 .variants
430 .iter()
431 .map(|constructor| TypeConstructorInterface {
432 // TODO: Find documentation
433 documentation: None,
434 name: constructor.name.clone(),
435 parameters: constructor
436 .parameters
437 .iter()
438 .map(|arg| ParameterInterface {
439 label: arg.label.clone(),
440 // We share the same id_map between each step so that the
441 // incremental ids assigned are consisten with each other
442 type_: from_type_helper(
443 arg.type_.as_ref(),
444 &mut id_map,
445 ),
446 })
447 .collect(),
448 })
449 .collect(),
450 None => vec![],
451 }
452 },
453 },
454 );
455 }
456
457 for (name, alias) in interface
458 .type_aliases
459 .iter()
460 .filter(|(_, v)| v.publicity.is_public())
461 {
462 let _ = type_aliases.insert(
463 name.clone(),
464 TypeAliasInterface {
465 documentation: alias.documentation.clone(),
466 deprecation: DeprecationInterface::from_deprecation(&alias.deprecation),
467 parameters: alias.arity,
468 alias: TypeInterface::from_type(&alias.type_),
469 },
470 );
471 }
472
473 for (name, value) in interface
474 .values
475 .iter()
476 .filter(|(_, v)| v.publicity.is_public())
477 {
478 match (value.type_.as_ref(), value.variant.clone()) {
479 (
480 Type::Fn {
481 args: arguments,
482 retrn: return_type,
483 },
484 ValueConstructorVariant::ModuleFn {
485 documentation,
486 implementations,
487 ..
488 },
489 ) => {
490 let mut id_map = IdMap::new();
491
492 let _ = functions.insert(
493 name.clone(),
494 FunctionInterface {
495 implementations: ImplementationsInterface::from_implementations(
496 &implementations,
497 ),
498 deprecation: DeprecationInterface::from_deprecation(&value.deprecation),
499 documentation,
500 parameters: arguments
501 .iter()
502 .map(|arg| ParameterInterface {
503 // TODO: fixme
504 label: None,
505 type_: from_type_helper(arg, &mut id_map),
506 })
507 .collect(),
508 return_: from_type_helper(&return_type, &mut id_map),
509 },
510 );
511 }
512
513 (
514 type_,
515 ValueConstructorVariant::ModuleConstant {
516 documentation,
517 implementations,
518 ..
519 },
520 ) => {
521 let _ = constants.insert(
522 name.clone(),
523 ConstantInterface {
524 implementations: ImplementationsInterface::from_implementations(
525 &implementations,
526 ),
527 type_: TypeInterface::from_type(type_),
528 deprecation: DeprecationInterface::from_deprecation(&value.deprecation),
529 documentation,
530 },
531 );
532 }
533
534 _ => {}
535 }
536 }
537
538 ModuleInterface {
539 documentation: interface.documentation.clone(),
540 types,
541 type_aliases,
542 constants,
543 functions,
544 }
545 }
546}
547
548impl TypeInterface {
549 fn from_type(type_: &Type) -> TypeInterface {
550 from_type_helper(type_, &mut IdMap::new())
551 }
552}
553
554/// Turns a type into its interface, an `IdMap` is needed to make sure that all
555/// the type variables' ids that appear in the type are mapped to an incremental
556/// number and consistent with each other (that is, two types variables that
557/// have the same id will also have the same incremental number in the end).
558fn from_type_helper(type_: &Type, id_map: &mut IdMap) -> TypeInterface {
559 match type_ {
560 Type::Fn { args, retrn } => TypeInterface::Fn {
561 parameters: args
562 .iter()
563 .map(|arg| from_type_helper(arg.as_ref(), id_map))
564 .collect(),
565 return_: Box::new(from_type_helper(retrn, id_map)),
566 },
567
568 Type::Tuple { elems } => TypeInterface::Tuple {
569 elements: elems
570 .iter()
571 .map(|elem| from_type_helper(elem.as_ref(), id_map))
572 .collect(),
573 },
574
575 Type::Var { type_ } => match type_
576 .as_ref()
577 .try_borrow()
578 .expect("borrow type after inference")
579 .deref()
580 {
581 TypeVar::Link { type_ } => from_type_helper(type_, id_map),
582 // Since package serialisation happens after inference there
583 // should be no unbound type variables.
584 // TODO: This branch should be `unreachable!()` but because of
585 // https://github.com/gleam-lang/gleam/issues/2533
586 // we sometimes end up with those in top level
587 // definitions.
588 // However, `Unbound` and `Generic` ids are generated
589 // using the same generator so we have no problem treating
590 // unbound variables as generic ones since ids will never
591 // overlap.
592 // Once #2533 is closed this branch can be turned back to
593 // be unreachable!().
594 TypeVar::Unbound { id } | TypeVar::Generic { id } => TypeInterface::Variable {
595 id: id_map.map_id(*id),
596 },
597 },
598
599 Type::Named {
600 name,
601 module,
602 args,
603 package,
604 ..
605 } => TypeInterface::Named {
606 name: name.clone(),
607 package: package.clone(),
608 module: module.clone(),
609 parameters: args
610 .iter()
611 .map(|arg| from_type_helper(arg.as_ref(), id_map))
612 .collect(),
613 },
614 }
615}
616
617/// This is a map that is used to map type variable id's to progressive numbers
618/// starting from 0.
619/// After type inference the ids associated with type variables can be quite
620/// high and are not the best to produce a human/machine readable output.
621///
622/// Imagine a function like this one: `pub fn wibble(item: a, rest: b) -> c`
623/// What we want here is for type variables to have increasing ids starting from
624/// 0: `a` with id `0`, `b` with id `1` and `c` with id `2`.
625///
626/// This map allows us to keep track of the ids we've run into and map those to
627/// their incremental counterpart starting from 0.
628struct IdMap {
629 next_id: u64,
630 ids: HashMap<u64, u64>,
631}
632
633impl IdMap {
634 /// Create a new map that will assign id numbers starting from 0.
635 fn new() -> IdMap {
636 IdMap {
637 next_id: 0,
638 ids: HashMap::new(),
639 }
640 }
641
642 /// Map an id to its mapped counterpart starting from 0. If an id has never
643 /// been seen before it will be assigned a new incremental number.
644 fn map_id(&mut self, id: u64) -> u64 {
645 match self.ids.get(&id) {
646 Some(mapped_id) => *mapped_id,
647 None => {
648 let mapped_id = self.next_id;
649 let _ = self.ids.insert(id, mapped_id);
650 self.next_id += 1;
651 mapped_id
652 }
653 }
654 }
655
656 /// If the type is a type variable, and has not been seen before, it will
657 /// be assigned to a new incremental number.
658 fn add_type_variable_id(&mut self, type_: &Type) {
659 match type_ {
660 // These types have no id to add to the map.
661 Type::Named { .. } | Type::Fn { .. } | Type::Tuple { .. } => (),
662 // If the type is actually a type variable whose id needs to be mapped.
663 Type::Var { type_ } => match type_
664 .as_ref()
665 .try_borrow()
666 .expect("borrow type after inference")
667 .deref()
668 {
669 TypeVar::Link { .. } => (),
670 TypeVar::Unbound { id } | TypeVar::Generic { id } => {
671 let _ = self.map_id(*id);
672 }
673 },
674 }
675 }
676}