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
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}