Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / package_interface.rs
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 Wasm external 241 /// code. That means that the function will use Wasm code through FFI when 242 /// compiled for the Wasm target. 243 uses_wasm_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 Wasm target, either due to a 253 /// pure Gleam implementation or an implementation that uses some Wasm 254 /// externals. 255 can_run_on_wasm: 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_wasm_externals, 274 can_run_on_erlang, 275 can_run_on_javascript, 276 can_run_on_wasm, 277 } = implementations; 278 279 ImplementationsInterface { 280 gleam: *gleam, 281 uses_erlang_externals: *uses_erlang_externals, 282 uses_javascript_externals: *uses_javascript_externals, 283 uses_wasm_externals: *uses_wasm_externals, 284 can_run_on_erlang: *can_run_on_erlang, 285 can_run_on_javascript: *can_run_on_javascript, 286 can_run_on_wasm: *can_run_on_wasm, 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}