Fork of daniellemaywood.uk/gleam — Wasm codegen work
Something went wrong. Try again.
26 kB · 698 lines
Rust
at wasm
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699// SPDX-License-Identifier: Apache-2.0// SPDX-FileCopyrightText: 2024 The Gleam contributors
use std::{collections::HashMap, ops::Deref};
use ecow::EcoString;use serde::Serialize;
#[cfg(test)]mod tests;
use crate::{ io::ordered_map, type_::{ self, Deprecation, Opaque, Type, TypeConstructor, TypeVar, TypeVariantConstructors, ValueConstructorVariant, expression::Implementations, },};
use crate::build::Package;
/// The public interface of a package that gets serialised as a json object.#[derive(Serialize, Debug)]#[serde(rename_all = "kebab-case")]pub struct PackageInterface { name: EcoString, version: EcoString, /// The Gleam version constraint that the package specifies in its `gleam.toml`. gleam_version_constraint: Option<EcoString>, /// A map from module name to its interface. #[serde(serialize_with = "ordered_map")] modules: HashMap<EcoString, ModuleInterface>,}
#[derive(Serialize, Debug)]#[serde(rename_all = "kebab-case")]pub struct ModuleInterface { /// A vector with the lines composing the module's documentation (that is /// every line preceded by a `////`). documentation: Vec<EcoString>, /// A map from type alias name to its interface. #[serde(serialize_with = "ordered_map")] type_aliases: HashMap<EcoString, TypeAliasInterface>, /// A map from type name to its interface. #[serde(serialize_with = "ordered_map")] types: HashMap<EcoString, TypeDefinitionInterface>, /// A map from constant name to its interface. #[serde(serialize_with = "ordered_map")] constants: HashMap<EcoString, ConstantInterface>, /// A map from function name to its interface. #[serde(serialize_with = "ordered_map")] functions: HashMap<EcoString, FunctionInterface>,}
#[derive(Serialize, Debug)]#[serde(rename_all = "kebab-case")]pub struct TypeDefinitionInterface { /// The definition's documentation comment (that is every line preceded by /// `///`). documentation: Option<EcoString>, /// If the definition has a deprecation annotation `@deprecated("...")` /// this field will hold the reason of the deprecation. deprecation: Option<DeprecationInterface>, /// The number of type variables in the type definition. /// ```gleam /// /// This type has 2 type variables. /// type Result(a, b) { /// Ok(a) /// Error(b) /// } /// ``` parameters: usize, /// A list of the type constructors. If the type is marked as opaque it /// won't have any visible constructors. constructors: Vec<TypeConstructorInterface>,}
#[derive(Serialize, Debug)]#[serde(rename_all = "kebab-case")]pub struct TypeConstructorInterface { /// The constructor's documentation comment (that is every line preceded by /// `///`). documentation: Option<EcoString>, /// The name of the type constructor. /// ```gleam /// pub type Box(a) { /// MyBox(value: a) /// //^^^^^ This is the constructor's name /// } /// ``` name: EcoString, /// A list of the parameters needed by the constructor. /// ```gleam /// pub type Box(a) { /// MyBox(value: a) /// // ^^^^^^^^ This is the constructor's parameter. /// } /// ``` parameters: Vec<ParameterInterface>,}
#[derive(Serialize, Debug)]#[serde(rename_all = "kebab-case")]pub struct TypeAliasInterface { /// The constructor's documentation comment (that is every line preceded by /// `///`). documentation: Option<EcoString>, /// If the alias has a deprecation annotation `@deprecated("...")` /// this field will hold the reason of the deprecation. deprecation: Option<DeprecationInterface>, /// The number of type variables in the type alias definition. /// ```gleam /// /// This type alias has 2 type variables. /// type Results(a, b) = List(Restul(a, b)) /// ``` parameters: usize, /// The aliased type. /// ```gleam /// type Ints = List(Int) /// // ^^^^^^^^^ This is the aliased type in a type alias. /// ``` alias: TypeInterface,}
#[derive(Serialize, Debug)]#[serde(rename_all = "kebab-case")]pub struct ConstantInterface { /// The constant's documentation comment (that is every line preceded by /// `///`). documentation: Option<EcoString>, /// If the constant has a deprecation annotation `@deprecated("...")` /// this field will hold the reason of the deprecation. deprecation: Option<DeprecationInterface>, implementations: ImplementationsInterface, /// The constant's type. #[serde(rename = "type")] type_: TypeInterface,}
/// A module's function. This differs from a simple `Fn` type as its arguments/// can be labelled.#[derive(Serialize, Debug)]#[serde(rename_all = "kebab-case")]pub struct FunctionInterface { /// The function's documentation comment (that is every line preceded by /// `///`). documentation: Option<EcoString>, /// If the constant has a deprecation annotation `@deprecated("...")` /// this field will hold the reason of the deprecation. deprecation: Option<DeprecationInterface>, implementations: ImplementationsInterface, parameters: Vec<ParameterInterface>, #[serde(rename = "return")] return_: TypeInterface,}
/// Informations about how a value is implemented.#[derive(Debug, Serialize, Copy, Clone)]#[serde(rename_all = "kebab-case")]pub struct ImplementationsInterface { /// Set to `true` if the const/function has a pure Gleam implementation /// (that is, it never uses external code). /// Being pure Gleam means that the function will support all Gleam /// targets, even future ones that are not present to this day. /// /// Consider the following function: /// /// ```gleam /// @external(erlang, "wibble", "wobble") /// pub fn a_random_number() -> Int { /// 4 /// // This is a default implementation. /// } /// ``` /// /// The implementations for this function will look like this: /// /// ```json /// { /// gleam: true, /// can_run_on_erlang: true, /// can_run_on_javascript: true, /// uses_erlang_externals: true, /// uses_javascript_externals: false, /// } /// ``` /// /// - `gleam: true` means that the function has a pure Gleam implementation /// and thus it can be used on all Gleam targets with no problems. /// - `can_run_on_erlang: false` the function can be called on the Erlang /// target. /// - `can_run_on_javascript: true` the function can be called on the JavaScript /// target. /// - `uses_erlang_externals: true` means that the function will use Erlang /// external code when compiled to the Erlang target. /// - `uses_javascript_externals: false` means that the function won't use /// JavaScript external code when compiled to JavaScript. The function can /// still be used on the JavaScript target since it has a pure Gleam /// implementation. gleam: bool, /// Set to `true` if the const/function is defined using Erlang external /// code. That means that the function will use Erlang code through FFI when /// compiled for the Erlang target. uses_erlang_externals: bool, /// Set to `true` if the const/function is defined using JavaScript external /// code. That means that the function will use JavaScript code through FFI /// when compiled for the JavaScript target. /// /// Let's have a look at an example: /// /// ```gleam /// @external(javascript, "wibble", "wobble") /// pub fn javascript_only() -> Int /// ``` /// /// It's implementations field will look like this: /// /// ```json /// { /// gleam: false, /// can_run_on_erlang: false, /// can_run_on_javascript: true, /// uses_erlang_externals: false, /// uses_javascript_externals: true, /// } /// ``` /// /// - `gleam: false` means that the function doesn't have a pure Gleam /// implementations. This means that the function is only defined using /// externals and can only be used on some targets. /// - `can_run_on_erlang: false` the function cannot be called on the Erlang /// target. /// - `can_run_on_javascript: true` the function can be called on the JavaScript /// target. /// - `uses_erlang_externals: false` the function is not using external /// Erlang code. /// - `uses_javascript_externals: true` the function is using JavaScript /// external code. uses_javascript_externals: bool, /// Set to `true` if the const/function is defined using Wasm external /// code. That means that the function will use Wasm code through FFI when /// compiled for the Wasm target. uses_wasm_externals: bool, /// Whether the function can be called on the Erlang target, either due to a /// pure Gleam implementation or an implementation that uses some Erlang /// externals. can_run_on_erlang: bool, /// Whether the function can be called on the JavaScript target, either due /// to a pure Gleam implementation or an implementation that uses some /// JavaScript externals. can_run_on_javascript: bool, /// Whether the function can be called on the Wasm target, either due to a /// pure Gleam implementation or an implementation that uses some Wasm /// externals. can_run_on_wasm: bool,}
impl ImplementationsInterface { fn from_implementations(implementations: &Implementations) -> ImplementationsInterface { // It might look a bit silly to just recreate an identical structure with // a different name. However, this way we won't inadvertently cause breaking // changes if we were to change the names used by the `Implementations` struct // that is used by the target tracking algorithm. // By doing this we can change the target tracking and package interface // separately! // // This pattern matching makes sure we will remember to handle any change // in the `Implementations` struct. let Implementations { gleam, uses_erlang_externals, uses_javascript_externals, uses_wasm_externals, can_run_on_erlang, can_run_on_javascript, can_run_on_wasm, } = implementations;
ImplementationsInterface { gleam: *gleam, uses_erlang_externals: *uses_erlang_externals, uses_javascript_externals: *uses_javascript_externals, uses_wasm_externals: *uses_wasm_externals, can_run_on_erlang: *can_run_on_erlang, can_run_on_javascript: *can_run_on_javascript, can_run_on_wasm: *can_run_on_wasm, } }}
#[derive(Serialize, Debug)]#[serde(rename_all = "kebab-case")]pub struct DeprecationInterface { /// The reason for the deprecation. message: EcoString,}
impl DeprecationInterface { fn from_deprecation(deprecation: &Deprecation) -> Option<DeprecationInterface> { match deprecation { Deprecation::NotDeprecated => None, Deprecation::Deprecated { message } => Some(DeprecationInterface { message: message.clone(), }), } }}
#[derive(Serialize, Debug)]#[serde(tag = "kind")]#[serde(rename_all = "kebab-case")]pub enum TypeInterface { /// A tuple type like `#(Int, Float)`. Tuple { /// The types composing the tuple. elements: Vec<TypeInterface>, }, /// A function type like `fn(Int, String) -> String`. Fn { parameters: Vec<TypeInterface>, #[serde(rename = "return")] return_: Box<TypeInterface>, }, /// A type variable. /// ```gleam /// pub fn wibble(value: a) -> a {} /// // ^ This is a type variable. /// ``` Variable { id: u64 }, /// A custom named type. /// ```gleam /// let value: Bool = True /// ^^^^ This is a named type. /// ``` /// /// All prelude types - like Bool, String, etc. - are named types as well. /// In that case their package is an empty string `""` and their module /// name is the string `"gleam"`. /// Named { name: EcoString, /// The package the type is defined in. package: EcoString, /// The module the type is defined in. module: EcoString, /// The type parameters that might be needed to define a named type. /// ```gleam /// let result: Result(Int, e) = Ok(1) /// // ^^^^^^ The `Result` named type has 2 parameters. /// // In this case it's the Int type and a type /// // variable. /// ``` parameters: Vec<TypeInterface>, },}
#[derive(Serialize, Debug)]#[serde(rename_all = "kebab-case")]pub struct ParameterInterface { /// If the parameter is labelled this will hold the label's name. /// ```gleam /// pub fn repeat(times n: Int) -> List(Int) /// // ^^^^^ This is the parameter's label. /// ``` label: Option<EcoString>, /// The parameter's type. /// ```gleam /// pub fn repeat(times n: Int) -> List(Int) /// // ^^^ This is the parameter's type. /// ``` #[serde(rename = "type")] type_: TypeInterface,}
impl PackageInterface { pub fn from_package( package: &Package, cached_modules: &im::HashMap<EcoString, type_::ModuleInterface>, ) -> PackageInterface { PackageInterface { name: package.config.name.clone(), version: package.config.version.to_string().into(), gleam_version_constraint: package .config .gleam_version .clone() .map(|version| EcoString::from(version.hex().to_string())), modules: package .modules .iter() .map(|module| &module.ast.type_info) .chain( package .cached_module_names .iter() .filter_map(|name| cached_modules.get(name)), ) .filter(|module| !package.config.is_internal_module(module.name.as_str())) .map(|module| (module.name.clone(), ModuleInterface::from_interface(module))) .collect(), } }}
impl ModuleInterface { fn from_interface(interface: &type_::ModuleInterface) -> ModuleInterface { let mut types = HashMap::new(); let mut type_aliases = HashMap::new(); let mut constants = HashMap::new(); let mut functions = HashMap::new(); for (name, constructor) in interface.types.iter().filter(|(name, c)| { // Aliases are stored separately c.publicity.is_public() && !interface.type_aliases.contains_key(*name) }) { let mut id_map = IdMap::new();
let TypeConstructor { deprecation, documentation, .. } = constructor;
for typed_parameter in &constructor.parameters { id_map.add_type_variable_id(typed_parameter.as_ref()); }
let _ = types.insert( name.clone(), TypeDefinitionInterface { documentation: documentation.clone(), deprecation: DeprecationInterface::from_deprecation(deprecation), parameters: interface .types .get(&name.clone()) .map_or(vec![], |t| t.parameters.clone()) .len(), constructors: match interface.types_value_constructors.get(&name.clone()) { Some(TypeVariantConstructors { variants, opaque: Opaque::NotOpaque, .. }) => variants .iter() .map(|constructor| TypeConstructorInterface { documentation: constructor.documentation.clone(), name: constructor.name.clone(), parameters: constructor .parameters .iter() .map(|arg| ParameterInterface { label: arg.label.clone(), // We share the same id_map between each step so that the // incremental ids assigned are consisten with each other type_: from_type_with_ids(arg.type_.as_ref(), &mut id_map), }) .collect(), }) .collect(), Some(_) | None => Vec::new(), }, }, ); }
for (name, alias) in interface .type_aliases .iter() .filter(|(_, v)| v.publicity.is_public()) { let mut id_map = IdMap::new();
for typed_parameter in alias.parameters.iter() { id_map.add_type_variable_id(typed_parameter.as_ref()); }
let _ = type_aliases.insert( name.clone(), TypeAliasInterface { documentation: alias.documentation.clone(), deprecation: DeprecationInterface::from_deprecation(&alias.deprecation), parameters: alias.arity, alias: from_type_with_ids(&alias.type_, &mut id_map), }, ); }
for (name, value) in interface .values .iter() .filter(|(_, v)| v.publicity.is_public()) { match (value.type_.as_ref(), value.variant.clone()) { ( Type::Fn { arguments, return_: return_type, }, ValueConstructorVariant::ModuleFn { documentation, implementations, field_map, .. }, ) => { let mut id_map = IdMap::new();
let reverse_field_map = field_map .as_ref() .map(|field_map| field_map.indices_to_labels()) .unwrap_or_default();
let _ = functions.insert( name.clone(), FunctionInterface { implementations: ImplementationsInterface::from_implementations( &implementations, ), deprecation: DeprecationInterface::from_deprecation(&value.deprecation), documentation, parameters: arguments .iter() .enumerate() .map(|(index, type_)| ParameterInterface { label: reverse_field_map .get(&(index as u32)) .map(|label| (*label).clone()), type_: from_type_with_ids(type_, &mut id_map), }) .collect(), return_: from_type_with_ids(return_type, &mut id_map), }, ); }
( type_, ValueConstructorVariant::ModuleConstant { documentation, implementations, .. }, ) => { let _ = constants.insert( name.clone(), ConstantInterface { implementations: ImplementationsInterface::from_implementations( &implementations, ), type_: TypeInterface::from_type(type_), deprecation: DeprecationInterface::from_deprecation(&value.deprecation), documentation, }, ); }
_ => {} } }
ModuleInterface { documentation: interface.documentation.clone(), types, type_aliases, constants, functions, } }}
impl TypeInterface { fn from_type(type_: &Type) -> TypeInterface { from_type_with_ids(type_, &mut IdMap::new()) }}
/// Turns a type into its interface, an `IdMap` is needed to make sure that all/// the type variables' ids that appear in the type are mapped to an incremental/// number and consistent with each other (that is, two types variables that/// have the same id will also have the same incremental number in the end).fn from_type_with_ids(type_: &Type, id_map: &mut IdMap) -> TypeInterface { match type_ { Type::Fn { arguments, return_ } => TypeInterface::Fn { parameters: arguments .iter() .map(|argument| from_type_with_ids(argument.as_ref(), id_map)) .collect(), return_: Box::new(from_type_with_ids(return_, id_map)), },
Type::Tuple { elements } => TypeInterface::Tuple { elements: elements .iter() .map(|element| from_type_with_ids(element.as_ref(), id_map)) .collect(), },
Type::Var { type_ } => match type_ .as_ref() .try_borrow() .expect("borrow type after inference") .deref() { TypeVar::Link { type_ } => from_type_with_ids(type_, id_map), // Since package serialisation happens after inference there // should be no unbound type variables. // TODO: This branch should be `unreachable!()` but because of // https://github.com/gleam-lang/gleam/issues/2533 // we sometimes end up with those in top level // definitions. // However, `Unbound` and `Generic` ids are generated // using the same generator so we have no problem treating // unbound variables as generic ones since ids will never // overlap. // Once #2533 is closed this branch can be turned back to // be unreachable!(). TypeVar::Unbound { id } | TypeVar::Generic { id } => TypeInterface::Variable { id: id_map.map_id(*id), }, },
Type::Named { name, module, arguments, package, .. } => TypeInterface::Named { name: name.clone(), package: package.clone(), module: module.clone(), parameters: arguments .iter() .map(|argument| from_type_with_ids(argument.as_ref(), id_map)) .collect(), }, }}
/// This is a map that is used to map type variable id's to progressive numbers/// starting from 0./// After type inference the ids associated with type variables can be quite/// high and are not the best to produce a human/machine readable output.////// Imagine a function like this one: `pub fn wibble(item: a, rest: b) -> c`/// What we want here is for type variables to have increasing ids starting from/// 0: `a` with id `0`, `b` with id `1` and `c` with id `2`.////// This map allows us to keep track of the ids we've run into and map those to/// their incremental counterpart starting from 0.struct IdMap { next_id: u64, ids: HashMap<u64, u64>,}
impl IdMap { /// Create a new map that will assign id numbers starting from 0. fn new() -> IdMap { IdMap { next_id: 0, ids: HashMap::new(), } }
/// Map an id to its mapped counterpart starting from 0. If an id has never /// been seen before it will be assigned a new incremental number. fn map_id(&mut self, id: u64) -> u64 { match self.ids.get(&id) { Some(mapped_id) => *mapped_id, None => { let mapped_id = self.next_id; let _ = self.ids.insert(id, mapped_id); self.next_id += 1; mapped_id } } }
/// If the type is a type variable, and has not been seen before, it will /// be assigned to a new incremental number. fn add_type_variable_id(&mut self, type_: &Type) { match type_ { // These types have no id to add to the map. Type::Named { .. } | Type::Fn { .. } | Type::Tuple { .. } => (), // If the type is actually a type variable whose id needs to be mapped. Type::Var { type_ } => match type_ .as_ref() .try_borrow() .expect("borrow type after inference") .deref() { TypeVar::Link { .. } => (), TypeVar::Unbound { id } | TypeVar::Generic { id } => { let _ = self.map_id(*id); } }, } }}